The PyMiershttp://pp.pymi.vn/2023-05-09T00:00:00+07:00Refactor 10 dòng code thành 90 dòng2023-05-09T00:00:00+07:002023-05-09T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2023-05-09:/article/refactor/<p>refactor không phải để code ngắn đi</p><p>Refactor là một phần công việc không thể thiếu của lập trình viên, sau khi code xong chạy được mà "chưa đẹp", lập trình viên sẽ chỉnh sửa đoạn code cho "đẹp" hơn, cũng có thể nhanh hơn, cũng có thể ngắn hơn, cũng có thể dễ hiểu hơn...</p>
<p>Vấn đề với refactor: là một khái niệm chung chung, không có ví dụ cụ thể, khó học/luyện tập để trở thành 1 kỹ năng. Best-practice của ngôn ngữ này lại có thể là điều không ai làm ở ngôn ngữ khác. Sách vở viết về refactor cũng chỉ có 1 quyển được cộng đồng mạng nhắc tới ?!!! <a href="https://martinfowler.com/books/refactoring.html">Refactoring - Improving the Design of Existing Code by Martin Fowler, with Kent Beck, 2018</a>. PyMi cũng từng có 1 bài viết giới thiệu việc <a href="http://pp.pymi.vn/article/repl/">refactoring code kèm với IPython</a>.</p>
<p><center>
<img alt="Refactor" src="https://images.unsplash.com/photo-1545697729-0ab8f5b1954c?ixlib=rb-4.0.3&q=85&fm=jpg&crop=entropy&cs=srgb&dl=zoltan-tasi-CLJeQCr2F_A-unsplash.jpg&w=640"></p>
<p>Photo by <a href="https://unsplash.com/@zoltantasi?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Zoltan Tasi</a> on <a href="https://unsplash.com/photos/CLJeQCr2F_A?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></center></p>
<p>Bài viết này dựa trên chapter 12 trong cuốn <a href="https://doc.rust-lang.org/book/ch12-03-improving-error-handling-and-modularity.html">The Rust Programming Language</a> (còn hay gọi là <strong>The book</strong>), kèm phóng tác, chuyển dịch sang Python, thêm "bình phẩm" và nhiều nhiều câu hỏi. Sử dụng ví dụ từ sách Rust khiến người đọc có thể tin cậy trình độ của tác giả... Steve Klabnik, Carol Nichols - những cái tên nổi tiếng bậc nhất trong thế giới Rust.</p>
<h3>Refactoring là gì</h3>
<p>Trong tiếng Anh, refactoring có nghĩa:</p>
<p><a href="https://dictionary.cambridge.org/dictionary/english/refactoring">cambridge</a>:</p>
<blockquote>
<p>refactoring isn’t in the Cambridge Dictionary yet. You can help!</p>
</blockquote>
<p><a href="https://www.dictionary.com/misspelling?term=refactoring">dictionary.com</a>:</p>
<blockquote>
<p>No results found for refactoring</p>
</blockquote>
<p>theo từ điển tiếng Anh, từ <strong>refactoring</strong> không tồn tại.</p>
<p>Theo <a href="https://en.wikipedia.org/wiki/Code_refactoring">Wikipedia</a>:</p>
<blockquote>
<p>In computer programming and software design, code refactoring is the process of restructuring existing computer code—changing the factoring—without changing its external behavior</p>
</blockquote>
<p>là việc chỉnh sửa lại code đã tồn tại mà không thay đổi tính năng của code.</p>
<p>Theo quảng cáo của cuốn sách về <a href="https://martinfowler.com/books/refactoring.html">Refactoring</a> nối tiếng nhất:</p>
<blockquote>
<p>Refactoring is a controlled technique for improving the design of an existing code base. Its essence is applying a series of small behavior-preserving transformations, each of which "too small to be worth doing". However the cumulative effect of each of these transformations is quite significant. By doing them in small steps you reduce the risk of introducing errors. You also avoid having the system broken while you are carrying out the restructuring - which allows you to gradually refactor a system over an extended period of time.</p>
</blockquote>
<p>Ở đây dùng <strong>refactoring</strong> như 1 danh từ, <strong>refactor</strong> như 1 động từ.</p>
<h3>Refactoring để làm gì</h3>
<p>Refactor để cải thiện, nâng cao</p>
<ul>
<li>Tính dễ bảo trì, fix bug: Maintainability.</li>
<li>Tính dễ mở rộng/thay đổi: Extensibility.</li>
<li>Tốc độ chạy: Performance.</li>
</ul>
<h3>Ví dụ refactor 10 dòng code</h3>
<p>Viết 1 chương trình giống như câu lệnh grep trên UNIX, tức nhận 2 đầu vào trên dòng lệnh là "từ khóa tìm kiếm" và tên file, in ra màn hình các dòng chứa từ khóa ấy.
Ví dụ tìm từ <code>root</code> trong file <code>/etc/passwd</code> trên Ubuntu, MacOS,...</p>
<div class="highlight"><pre><span></span><code>$ grep root /etc/passwd
root:x:0:0::/root:/bin/bash
</code></pre></div>
<p>Code Python 10 dòng, không tính ngoài function main, dịch trực tiếp từ <a href="https://doc.rust-lang.org/book/ch12-00-an-io-project.html">ví dụ Rust</a>:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># $ python3 grep.py root /etc/passwd</span>
<span class="c1"># root:x:0:0::/root:/bin/bash</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">contents</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">IOError</span><span class="p">:</span>
<span class="n">exit</span><span class="p">(</span><span class="s2">"Should have been able to read the file"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="o">.</span><span class="n">splitlines</span><span class="p">():</span>
<span class="k">if</span> <span class="n">query</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>Code này hoàn toàn ổn: sạch đẹp, đúng chuẩn PEP8, và quan trọng nhất: chạy ra đúng kết quả. Vậy có cần refactor không? Tùy.
Đó là chỗ khó khi nói về refactoring, không có gì rõ ràng, chính xác, mọi thứ đều "tùy".
Tùy vào:</p>
<ul>
<li>chương trình nhỏ hay to, 10 dòng hay 10000 dòng</li>
<li>chương trình dùng 1 lần hay trong dự án 1 năm ++</li>
<li>chạy 1 giây hay chạy 1 giờ mới xong</li>
<li>team 1 người hay 10 người code cùng</li>
</ul>
<p>Trong ví dụ cụ thể này, đoạn code trên hoàn toàn không cần refactor. Nhưng đây là bài viết về refactor, hãy xem tác giả sẽ biến đoạn code này thành 90 dòng ra sao.</p>
<h4>4+1 vấn đề của đoạn code</h4>
<ul>
<li>Function <code>main</code> thực hiện 2 công việc khác nhau. Khi chương trình lớn lên, <code>main</code> sẽ thực hiện thêm nhiều công việc nữa khiến cho nó khó hiểu, khó test, khó thay đổi 1 công việc mà không ảnh hưởng đến các công việc khác. Không test được: cách duy nhất để biết từng đoạn code trong main chạy đúng hay sai là chạy thử nó, với nhiều đầu vào khác nhau và dùng mắt kiểm tra kết quả. Nên tốt nhất là tách ra thành mỗi function thực hiện duy nhất 1 công việc + có thể viết unittest.</li>
<li>Biến <code>query</code> và <code>file_path</code> là "configuration" của chương trình, biến <code>contents</code> dùng để thực hiện logic. Khi <code>main</code> dài hơn, sẽ cần nhiều biến hơn, khi có nhiều biến hơn, sẽ khó để nhớ/theo dõi mục đích của từng biến. Nên tốt nhất là gộp các biến configuration vào 1 struct (class) để khiến mục đích của chúng rõ ràng.</li>
<li>Dù code đã in ra thông báo khi có exception lúc đọc file, nhưng có hơn 1 lý do có xảy ra exception khi đọc file: file không tồn tại, không có quyền để đọc file... In ra nội dung "không mở được file" không hề có ích cho người dùng biết vấn đề thực sự là gì.</li>
<li>Khi người dùng không đưa vào đủ 2 đầu vào trên dòng lệnh, sẽ xảy ra IndexError, exception này không giải thích rõ ràng tới người dùng chuyện gì xảy ra. Tốt nhất là đặt <strong>tất cả</strong> code xử lý exception vào chung 1 chỗ để sau này chỉ cần xem 1 chỗ nếu logic xử lý exception cần thay đổi, đồng thời giúp hiển thị nội dung lỗi rõ ràng dễ hiểu hơn tới người dùng.</li>
<li>Đoạn này không có trong bản Rust: việc đọc toàn bộ nội dung file vào RAM với <code>f.read()</code> là một "code smell", khiến đoạn code này không xử lý được file có kích thước lớn hơn RAM.</li>
</ul>
<h4>Thực hiện refactoring</h4>
<p>Function <code>main</code> chỉ nên giới hạn tính năng:</p>
<ul>
<li>gọi function xử lý các đầu vào từ dòng lệnh</li>
<li>setup các configuration (cấu hình) khác</li>
<li>gọi <code>run</code> function trong file khác, ví dụ lib.py</li>
<li>xử lý exception có thể xảy ra</li>
</ul>
<h5>Tách code xử lý đầu vào</h5>
<p>Viết function <code>parse_config</code> nhận các argument, trả về:</p>
<ul>
<li>tuple các config: <code>(query, content)</code>, nhưng function <code>main</code> gọi <code>parse_config</code> sẽ lại unpacking tuple này thành các biến khác nhau. Đây là dấu hiệu của việc sử dụng chưa đúng "abstraction". Việc trả về tuple cũng không "gắn" được <code>query</code> và <code>file_path</code> vào với config. Vậy nên</li>
<li>trả về 1 dictionary hoặc 1 Config object (Config class instance). Ở đây sẽ trả về 1 Config object cho giống ví dụ trong Rust.</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Config</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">query</span><span class="p">,</span> <span class="n">file_path</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span>
<span class="bp">self</span><span class="o">.</span><span class="n">file_path</span> <span class="o">=</span> <span class="n">file_path</span>
<span class="k">def</span> <span class="nf">parse_config</span><span class="p">(</span><span class="n">argv</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="n">Config</span><span class="p">:</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="k">return</span> <span class="n">Config</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">file_path</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">config</span> <span class="o">=</span> <span class="n">parse_config</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">contents</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">except</span> <span class="ne">IOError</span><span class="p">:</span>
<span class="n">exit</span><span class="p">(</span><span class="s2">"Should have been able to read the file"</span><span class="p">)</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="o">.</span><span class="n">splitlines</span><span class="p">():</span>
<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">query</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
</code></pre></div>
<h5>setup các configuration (cấu hình) khác</h5>
<p>Giả sử chương trình sẽ nhận thêm biến environment (môi trường) <code>IGNORE_CASE</code>, nếu được set, chương trình sẽ không phân biệt chữ hoa chữ thường khi tìm kiếm. Đây là ví dụ về chương trình có thể nhận config từ nhiều nguồn khác nhau, ta chỉ cần thay đổi class <code>Config</code> và <code>parse_config</code>:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Config</span><span class="p">:</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">query</span><span class="p">,</span> <span class="n">file_path</span><span class="p">,</span> <span class="n">ignore_case</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">query</span> <span class="o">=</span> <span class="n">query</span>
<span class="bp">self</span><span class="o">.</span><span class="n">file_path</span> <span class="o">=</span> <span class="n">file_path</span>
<span class="bp">self</span><span class="o">.</span><span class="n">ignore_case</span> <span class="o">=</span> <span class="n">ignore_case</span>
<span class="k">def</span> <span class="nf">parse_config</span><span class="p">(</span><span class="n">argv</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="n">Config</span><span class="p">:</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="n">ignore_case</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"IGNORE_CASE"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Config</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">file_path</span><span class="p">,</span> <span class="n">ignore_case</span><span class="p">)</span>
</code></pre></div>
<h5>gọi run function trong file khác, ví dụ lib.py</h5>
<p>Tách toàn bộ logic của chương trình, ngoại trừ phần xử lý exception ra 1 file khác tên <code>lib.py</code> và đặt tên function là <code>run</code>:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># lib.py</span>
<span class="c1"># Class Config...</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">Config</span><span class="p">):</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">contents</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="o">.</span><span class="n">splitlines</span><span class="p">():</span>
<span class="k">if</span> <span class="n">config</span><span class="o">.</span><span class="n">query</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">lib</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">config</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">parse_config</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">lib</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">IOError</span><span class="p">:</span>
<span class="n">exit</span><span class="p">(</span><span class="s2">"Should have been able to read the file"</span><span class="p">)</span>
</code></pre></div>
<p>Giờ đây <code>run</code> lại gặp vấn đề của <code>main</code>: làm nhiều việc, nhưng ít ra nó cũng chia bớt 2 việc cho <code>main</code> là parse config và xử lý exception. Khi một chương trình <code>run</code>, nó có thể thực hiện nhiều việc khác nhau, ở đây tiếp tục tách mỗi việc thành 1 function riêng. Việc đọc file không phức tạp hơn 1 dòng nên để nguyên, việc tìm kiếm string tách ra thành function <code>search</code>. Function <code>search</code> thay vì print, sẽ trả về 1 list chứa các string chứa từ khóa tìm kiếm, để việc print lại cho <code>run</code>. Như vậy có thể viết unittest kiểm tra logic của function quan trọng này thay vì kiểm tra bằng mắt.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">query</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">contents</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="o">.</span><span class="n">splitlines</span><span class="p">():</span>
<span class="k">if</span> <span class="n">query</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
<span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">return</span> <span class="n">result</span>
<span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">config</span><span class="p">:</span> <span class="n">Config</span><span class="p">):</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">contents</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">search</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">query</span><span class="p">,</span> <span class="n">contents</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
</code></pre></div>
<p>Test:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">unittest</span>
<span class="kn">import</span> <span class="nn">lib</span>
<span class="k">class</span> <span class="nc">TestGrep</span><span class="p">(</span><span class="n">unittest</span><span class="o">.</span><span class="n">TestCase</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">test_search_found</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">res</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="s2">"us"</span><span class="p">,</span> <span class="s2">"Among us</span><span class="se">\n</span><span class="s2">Some write Rust</span><span class="se">\n</span><span class="s2">Some write promt</span><span class="se">\n</span><span class="s2">All write Python"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="p">[</span><span class="s2">"Among us"</span><span class="p">,</span> <span class="s2">"Some write Rust"</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">test_search_not_found</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">res</span> <span class="o">=</span> <span class="n">lib</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="s2">"HTML"</span><span class="p">,</span> <span class="s2">"Among us</span><span class="se">\n</span><span class="s2">Some write Rust</span><span class="se">\n</span><span class="s2">Some write promt</span><span class="se">\n</span><span class="s2">All write Python"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">assertEqual</span><span class="p">(</span><span class="n">res</span><span class="p">,</span> <span class="p">[])</span>
</code></pre></div>
<p>Chạy test:</p>
<div class="highlight"><pre><span></span><code><span class="c">$ python3 </span><span class="nb">-</span><span class="c">m unittest test_grep</span><span class="nt">.</span><span class="c">py </span><span class="nb">-</span><span class="c">vvv</span>
<span class="c">test_search_found (test_grep</span><span class="nt">.</span><span class="c">TestGrep) </span><span class="nt">...</span><span class="c"> ok</span>
<span class="c">test_search_not_found (test_grep</span><span class="nt">.</span><span class="c">TestGrep) </span><span class="nt">...</span><span class="c"> ok</span>
<span class="nb">----------------------------------------------------------------------</span><span class="c"></span>
<span class="c">Ran 2 tests in 0</span><span class="nt">.</span><span class="c">000s</span>
<span class="c">OK</span>
</code></pre></div>
<h5>Thêm tính năng dễ dàng nhờ refactor</h5>
<p>CHÚ Ý: thêm tính năng không nằm trong phạm vi refactor, ở đây minh họa tác dụng của việc refactor.</p>
<p>Khi mọi tính năng cơ bản đã ổn định, viết thêm code để xử lý khi người dùng set env <code>IGNORE_CASE</code>. Có 2 cách xử lý:</p>
<ul>
<li>thêm 1 boolean argument cho function search, <code>search(query, contents, ignore_case=False)</code>. Việc dùng boolean argument còn được gọi là <a href="https://martinfowler.com/bliki/FlagArgument.html">flag argument</a> thường được xem như <a href="http://www.informit.com/articles/article.aspx?p=1392524">code smell</a> vì nó ám chỉ function này làm nhiều hơn 1 việc. Trong trường hợp này, khi <code>ignore_case=True</code> sẽ chỉ gọi thêm 1 method (<code>lower()</code>) với mỗi dòng, chứ không "làm việc khác" nên argument này hoàn toàn OK. Một ví dụ tương tự, trong Python sort, có argument <code>reverse=True</code> hoặc <code>False</code> để thay đổi thứ tự sắp xếp.</li>
<li>viết 1 function riêng <code>search_case_insensitive(query, contents)</code>, tác giả chọn phương án này mà không giải thích tại sao. Nhược điểm của phương án này là nếu phần code chung của 2 function là 30 bước, sẽ phải copy lại phần code chung. Nhưng cũng có thể cải thiện bằng việc tách riêng ra 1 function nữa như <code>search_internal</code> mà cả 2 <code>search</code>, <code>search_case_insensitive</code> cùng gọi.</li>
</ul>
<p>Ở đây ta sẽ làm giống như ví dụ trong phiên bản Rust:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">query</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">contents</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="o">.</span><span class="n">splitlines</span><span class="p">():</span>
<span class="k">if</span> <span class="n">query</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
<span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">return</span> <span class="n">result</span>
<span class="k">def</span> <span class="nf">search_case_insensitive</span><span class="p">(</span><span class="n">query</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">contents</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-></span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">]:</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">query</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="n">result</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="o">.</span><span class="n">splitlines</span><span class="p">():</span>
<span class="k">if</span> <span class="n">query</span> <span class="ow">in</span> <span class="n">line</span><span class="o">.</span><span class="n">lower</span><span class="p">():</span>
<span class="n">result</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">return</span> <span class="n">result</span>
</code></pre></div>
<h5>Xử lý exception</h5>
<p><code>main</code> cần xử lý exception từ 2 function <code>parse_config</code> và <code>run</code>, riêng biệt, để làm rõ hơn các exception đã xảy ra, ta tạo ra exception cho từng lỗi cụ thể.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">parse_config</span><span class="p">(</span><span class="n">argv</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="n">Config</span><span class="p">:</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="n">ignore_case</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"IGNORE_CASE"</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Config</span><span class="p">(</span><span class="n">query</span><span class="p">,</span> <span class="n">file_path</span><span class="p">,</span> <span class="n">ignore_case</span><span class="p">)</span>
</code></pre></div>
<p>function này có thể thiếu <code>query</code>, thiếu <code>file_path</code>, vậy viết:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">parse_config</span><span class="p">(</span><span class="n">argv</span><span class="p">:</span> <span class="nb">list</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="n">Config</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">query</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="k">except</span> <span class="ne">IndexError</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">IndexError</span><span class="p">(</span><span class="s2">"Didn't get a query string"</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="n">argv</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="k">except</span> <span class="ne">IndexError</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">IndexError</span><span class="p">(</span><span class="s2">"Didn't get a file path"</span><span class="p">)</span>
</code></pre></div>
<p>các exception này chứa nội dung mà người dùng có thể hiểu được.</p>
<p>Chạy thử với 3 trường hợp lỗi:</p>
<div class="highlight"><pre><span></span><code>$ python3 grep.py
Problem parsing arguments: Didn<span class="s1">'t get a query string</span>
<span class="s1">$ python3 grep.py root</span>
<span class="s1">Problem parsing arguments: Didn'</span>t get a file path
$ python3 grep.py root /etc/passssss
Application error: <span class="o">[</span>Errno <span class="m">2</span><span class="o">]</span> No such file or directory: <span class="s1">'/etc/passssss'</span>
</code></pre></div>
<p>Tới đây, chương 12 của <strong>The book</strong> kết thúc.</p>
<h3>Bonus: xử lý file kích thước lớn tùy ý</h3>
<p>thay</p>
<div class="highlight"><pre><span></span><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">contents</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">search</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">query</span><span class="p">,</span> <span class="n">contents</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
</code></pre></div>
<p>Thành</p>
<div class="highlight"><pre><span></span><code><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">file_path</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">search</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">query</span><span class="p">,</span> <span class="n">f</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
</code></pre></div>
<p>sử dụng <code>f</code> như 1 iterable, mỗi lần lấy 1 dòng ra, trong search, thay:</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="o">.</span><span class="n">splitlines</span><span class="p">():</span>
</code></pre></div>
<p>Thành:</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s2">"</span><span class="se">\r\n</span><span class="s2">"</span><span class="p">)</span>
</code></pre></div>
<p>Function <code>search</code> thay vì trả về 1 list sẽ yield từng dòng (generator).</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">search</span><span class="p">(</span><span class="n">query</span><span class="p">:</span> <span class="nb">str</span><span class="p">,</span> <span class="n">contents</span><span class="p">:</span> <span class="n">Iterable</span><span class="p">)</span> <span class="o">-></span> <span class="n">Generator</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">]:</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">contents</span><span class="p">:</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">rstrip</span><span class="p">(</span><span class="s2">"</span><span class="se">\r\n</span><span class="s2">"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">query</span> <span class="ow">in</span> <span class="n">line</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">line</span>
</code></pre></div>
<h4>So sánh code Rust và Python</h4>
<p>Phiên bản code cuối cùng sẽ khác một chút so với nội dung viết trong bài, nhằm làm giống phiên bản Rust nhất: ví dụ phần unittest sẽ chỉ viết 2 unittest như bản Rust, unittest bên trên bị xóa đi để so sánh cho công bằng.</p>
<p>Code Python ngắn hơn Rust một chút:</p>
<div class="highlight"><pre><span></span><code>$ wc -l grep.py test_grep.py lib.py
<span class="m">18</span> grep.py
<span class="m">25</span> test_grep.py
<span class="m">52</span> lib.py
<span class="m">95</span> total
$ wc -l src/main.rs src/lib.rs
<span class="m">16</span> src/main.rs
<span class="m">106</span> src/lib.rs <span class="c1"># rust viết test vào luôn file code</span>
<span class="m">122</span> total
</code></pre></div>
<p>Xem code:</p>
<ul>
<li><a href="http://pp.pymi.vn/refactor/grep.py">grep.py</a> - online <a href="https://glot.io/snippets/gky3ldwlxs">https://glot.io/snippets/gky3ldwlxs</a></li>
<li><a href="http://pp.pymi.vn/refactor/lib.py">lib.py</a> - online <a href="https://glot.io/snippets/gky3ldwlxs">https://glot.io/snippets/gky3ldwlxs</a></li>
<li><a href="http://pp.pymi.vn/refactor/main.rs">main.rs</a> - online <a href="https://gist.github.com/rust-play/5689af5322e9e26eb95458a159289f43">https://gist.github.com/rust-play/5689af5322e9e26eb95458a159289f43</a></li>
<li><a href="{stataic}/refactor/lib.rs">lib.rs</a> - online <a href="https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0f6695e6359b96aae6ca976cc23b5c07">https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=0f6695e6359b96aae6ca976cc23b5c07</a></li>
</ul>
<h4>Bài học</h4>
<ul>
<li>function <code>main</code> chỉ nên parse config và xử lý exception</li>
<li>tách riêng logic chương trình ra thành (các) file riêng, chứa các function con cho dễ test</li>
<li>viết unittest</li>
<li>tạo abstraction khi cần thiết (class), giúp chia rõ vai trò của các biến khác nhau</li>
<li>exception cần chứa thông tin có ích cho người dùng (thay vì chỉ developer mới hiểu được)</li>
<li>mọi thứ đều là tương đối</li>
</ul>
<h3>Kết luận</h3>
<p>Refactoring để nâng cao chất lượng (Maintainability, Extensibility, Performance) của code, không phải để làm ngắn.</p>
<h3>Tham khảo</h3>
<ul>
<li><a href="https://martinfowler.com/books/refactoring.html">https://martinfowler.com/books/refactoring.html</a></li>
<li><a href="https://doc.rust-lang.org/book/ch12-03-improving-error-handling-and-modularity.html">https://doc.rust-lang.org/book/ch12-03-improving-error-handling-and-modularity.html</a></li>
<li><a href="https://martinfowler.com/books/refactoring.html">https://martinfowler.com/books/refactoring.html</a></li>
<li><a href="https://martinfowler.com/bliki/FlagArgument.html">https://martinfowler.com/bliki/FlagArgument.html</a></li>
<li><a href="http://www.informit.com/articles/article.aspx?p=1392524">http://www.informit.com/articles/article.aspx?p=1392524</a></li>
</ul>
<p>Hết!</p>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>.</p>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
</ul>Làm an toàn thông tin cần học những gì?2022-12-04T00:00:00+07:002022-12-04T00:00:00+07:00minixtag:pp.pymi.vn,2022-12-04:/article/attt/<p>Nhân ngày cuối tuần không đi hack do quên reg giải CTF nên ngồi tổng hợp lại góc nhìn 2 năm theo đuổi ngành "phá làng phá xóm để giúp cho làng xóm không bị đứa khác phá". Dù chưa có thành tựu gì nhiều nhưng chiến đấu cùng các …</p><p>Nhân ngày cuối tuần không đi hack do quên reg giải CTF nên ngồi tổng hợp lại góc nhìn 2 năm theo đuổi ngành "phá làng phá xóm để giúp cho làng xóm không bị đứa khác phá". Dù chưa có thành tựu gì nhiều nhưng chiến đấu cùng các anh em PymiCTF giúp mình có dịp tìm được nhiều thứ hay ho. Sau đây là tóm tắt một số skill kỹ thuật mà một người trong ngành an toàn thông tin nên có.</p>
<h2>Các kỹ năng trong ngành an toàn thông tin</h2>
<p>Ngành an toàn thông tin được hiểu là ngành thực hiện các hình thức ngăn chặn, phát hiện, phòng ngừa việc truy cập, tiết lộ, chia sẻ, phát tán và sử dụng thông tin trái phép khi chưa được sự cho phép. Trong đó, an ninh mạng là một phần của ngành an toàn thông tin nhằm bảo vệ các thông tin trên hệ thống mạng khỏi sự xâm phạm, sử dụng trái phép, bảo vệ tính bí mật, toàn vẹn và khả dụng của thông tin.</p>
<p>Ngành an toàn thông tin là một ngành rộng với đa dạng các lĩnh vực chuyên môn, mỗi chuyên ngành đều yêu cầu một bộ các kỹ năng chuyên sâu đa dạng và cần thời gian để có thể thực hiện thành thạo. Về khía cạnh kỹ thuật, ngành an toàn thông tin phân chia các kỹ năng như sau:</p>
<ul>
<li>Penetration Testing: Kỹ năng kiểm thử xâm nhập, trong đó kiểm thử viên sẽ đánh giá toàn diện về bảo mật của hệ thống bằng cách sử dụng các kỹ thuật tấn công mà tin tặc thường sử dụng để kịp thời phát hiện nhiều lỗ hổng bảo mật trên hệ thống nhất có thể và đề xuất phương pháp giảm thiểu những nguy cơ do lỗ hổng đem lại.</li>
<li>Digital Forensics: Kỹ năng điều tra kỹ thuật số, trong đó điều tra viên sẽ thực hiện thu thập các bằng chứng kỹ thuật số nhằm đảm bảo tính liên quan, minh bạch và hợp pháp của các chứng cứ số, bảo vệ tính toàn vẹn của chứng cứ trước khi đưa ra trước tòa án.</li>
<li>Security Analysis: Kỹ năng phân tích bảo mật, trong đó các chuyên viên sẽ sử dụng kiến thức về phòng thủ và tấn công để phân tích dữ liệu thu được từ hệ thống thông tin, hạ tầng mạng, từ đó đưa ra những chuẩn đoán sớm về các dấu hiệu của một cuộc tấn công an ninh mạng và đưa ra phương án ngăn chặn.</li>
<li>Malware Analysis: Kỹ năng phân tích cách hoạt động của các loại phần mềm độc hại, tìm kiếm các tạo tác mà các phần mềm này tạo ra trên hệ thống từ đó hiểu mục đích, chiến thuật mà đối tượng tấn công sử dụng khi xâm nhập vào hệ thống thông tin.</li>
</ul>
<p>Ngoài các nhóm kỹ năng trên, sẽ có các nhóm kỹ năng chuyên sâu và nâng cao hơn để hỗ trợ như:</p>
<ul>
<li>Binary Exploit: Khai thác lỗ hổng bảo mật trong các tập tin thực thi, thư viện, yêu cầu thực hiện phải có kiến thức chuyên sâu về hoạt động của phần cứng như CPU, thanh ghi, cách phân bổ bộ nhớ trong hệ điều hành, kiến trúc và cách hoạt động ở tầng nhân của từng hệ điều hành riêng biệt như Windows và Linux, biết về ngôn ngữ assembly</li>
<li>Web Exploit: Khai thác lỗ hổng bảo mật trong ứng dụng web, yêu cầu người thực hiện phải có kiến thức về các ngôn ngữ lập trình ứng dụng web như PHP, Java, .NET, Python... và tận dụng các kiến thức này để phát hiện và khai thác thành công các lỗ hổng bảo mật có thể dùng để xâm nhập, trích xuất thông tin hoặc làm gián đoạn hệ thống thông qua các ứng dụng web này</li>
<li>Reverse Engineering: Kỹ thuật dịch ngược sử dụng để phân tích hoạt động của các phần mềm, hiểu được nguyên lý hoạt động của chúng từ đó có thể thực hiện thay đổi nhằm chuyển đổi chức năng ban đầu của phần mềm.</li>
<li>Open-source Intelligence: Tình báo nguồn mở là kỹ năng tìm kiếm thông tin trên mạng thông qua các công cụ mở như mạng xã hội, ứng dụng tìm kiếm để thu được thông tin về đối tượng dựa trên nhưng thông tin ban đầu được cung cấp.</li>
<li>Cryptography: Kỹ năng triển khai thực hiện các thuật toán và phương pháp được sử dụng để mã hóa thông tin. Qua đó có thể giữ tính bí mật, toàn vẹn của thông tin trong qua trình truyền tải hoặc dùng để giải mã những thông tin được che giấu.</li>
</ul>
<h2>Học các kỹ năng này như thế nào?</h2>
<p>Đối với một người chưa có kỹ năng gì về công nghệ thông tin thì trước tiên cần trang bị 3 thứ trước khi bắt đầu:</p>
<ul>
<li>Lập trình (Học Python ở Pymi giúp bạn tiết kiệm rất nhiều thời gian :v)</li>
<li>Mạng căn bản (Hiểu mạng hoạt động như thế nào, VD: hiểu tại sao mạng chậm? do cá mập cắn hay do cái khác...)</li>
<li>Kiến trúc máy tính và hệ điều hành (Thực ra cũng không cần biết nếu đi một số mảng như web security, nhưng biết thì sẽ biết cách tự tạo một mã khai thác sau này)
Sau khi đã có số kiến thức trên thì một số cách sau sẽ giúp nhanh tiến bộ:</li>
<li>Chơi CTF (Có thể đọc bài sau <a href="https://pp.pymi.vn/article/pymictf/">Ký sự một năm cướp giật (capture the flag)</a> của anh hvnsweeting)</li>
<li>Youtube, Twitch (Một số idol chia sẻ kiến thức miễn phí cho cộng đồng như nahamsec, ippsec...)</li>
<li>Theo dõi các security researchers (các chuyên gia "thật sự" trong ngành như angelboy, kienman0war...)</li>
<li>Tái hiện security bug sau đó tự khai thác (tự lập trình lại ứng dụng có tồn tại lỗi để phân tích, có thể sử dụng nguồn sau: <a href="https://www.shiftleft.io/community-and-training/vulnerability-fix-database/home/">Shiftleft</a>)</li>
<li>Thi chứng chỉ bảo mật như OSCP (nếu được tài trợ chứ học phí rất đắt)</li>
</ul>
<h2>Kết luận</h2>
<p>Ngành an toàn thông tin rất rộng và phức tạp, một người có thể master một trong các kỹ năng trên là đã kinh khủng lắm rồi. Nhiều anh em hacker phải bỏ thời gian, công sức (không đi chơi với người yêu cuối tuần, thức xuyên đêm hacking) để có thể theo ngành. Ngoài ra nếu không vững ý chí thì còn có thể gia nhập Juventus nữa.</p>
<p>H4CK F0r FUN 8U7 P3r51573NC3</p>
<p>Hết.</p>Ký sự một năm cướp giật (capture the flag)2022-10-02T00:00:00+07:002022-10-02T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2022-10-02:/article/pymictf/<p>1 năm sau SNYK CTF 2021, team PyMi đã đứng top 10 Việt Nam trên ctftime.org, học được rất rất nhiều.</p><p>Tháng 10 năm 2021, tình cờ tham gia SNYK CTF sau 1 tin quảng cáo dạo trên email, lập team sau vài ngày rồi cháy 24 giờ, <a href="http://pp.pymi.vn/article/ctf/">đứng thứ 42 bảng xếp hạng SNYK.io</a>.</p>
<p>Kể từ ngày ấy, đều đặn mỗi 2 ngày cuối tuần, <a href="https://ctftime.org/team/175619">team CTF PyMi</a> đi chiến 36 giải lớn nhỏ.
Từ newbie với ước mơ nhỏ bé "chỉ cần giải được 1 bài", giờ đã trở thành "thi thoảng mới giải được 1 bài, còn toàn giải được 2 bài trở lên".</p>
<p>Chơi CTF xong có một cảm giác thôi: sợ.
Sợ đến mức phải rút dây điện ra để không còn bị hack nữa.
Mọi sự an toàn đều chỉ là "ảo tưởng", đến cả <a href="https://www.imdb.com/title/tt3774114/">chính phủ các cường quốc G7 còn bị
hack</a>, thì mấy cái trang web lương lập trình viên 10 triệu lấy đâu ra mà còn.</p>
<p>Bài viết chỉ nói tới phần kỹ thuật liên quan đến hacking, còn một phần khác đáng
sợ không kém là "social hacking" là nguyên nhân của hàng rổ vụ hack có thể tham khảo <a href="https://www.theverge.com/2022/9/16/23356959/uber-hack-social-engineering-threats">trên in-tơ-nét</a>.</p>
<p><img alt="Ghost In The Wires" src="https://www.mitnicksecurity.com/hs-fs/hubfs/buy-ghost.png?width=448&name=buy-ghost.png">
<center><a href="https://www.mitnicksecurity.com/ghost-in-the-wires">Ghost In The Wires - cuốn sách về 1 hacker social-engineering số 1 thế giới</a></center></p>
<p>Có một câu nói khá khó nghe nhưng nổi tiếng:</p>
<blockquote>
<p>"There are two types of companies in the world: those that know they've been hacked and those that don't." - Misha Glenny</p>
</blockquote>
<p>(từ điển dịch: có 2 loại công ty trên thế giới: loại biết họ đã bị hack, và loại không biết).</p>
<p>Dưới đây là những bài học cho lập trình viên làm web (NGHỀ NGUY HIỂM),
và những thành tựu, kinh nghiệm thu được từ chơi CTF trong suốt các ngày cuối tuần của 1 năm.</p>
<p>PS: bài viết dưới góc độ một newbie trong ngành security.</p>
<h3>Các bài PHẢI học cho web developer</h3>
<h4>ĐÂY LÀ NGHỀ QUÁ NGUY HIỂM</h4>
<p>Bước chân vào nghề lập trình web tưởng chỉ cần biết thiết kế DB theo 3 normalization form,
dùng cái DB SQL & NoSQL, dùng framework xịn nhất, hiểu kiến trúc MVC, biết pull docker về build rồi bật là đủ (ăn).
Nhưng nếu trong đầu không được trang bị đầy đủ kiến thức về security thì ngành làm web như một bãi mìn đầy kinh sợ.
Cụ thể hơn, nếu bạn đang làm lập trình web mà công ty không có training, và bạn
cũng không hiểu đầy đủ (HIỂU không phải CHỈ là biết nghĩa) những từ đáng sợ sau, thì khả năng 96.69 %, công ty bạn
thuộc loại 2. Mà thậm chí hiểu đủ, cũng không làm cho bạn miễn dịch với hack.</p>
<p>Trường lớp học có thể dạy làm ra trang web, chạy được, đúng đủ tính năng,
những trang cá nhân này nếu có bị hack, cũng không có hậu quả lớn. Nhưng công ty,
muốn không bị hack, phải thực hiện nhiệm vụ training của mình, không làm tự chịu.</p>
<p>Các nghề khác cũng đều có nguy cơ bị hack dù là DevOps hay sysadmin, nhưng web là phổ biến nhất.</p>
<h4>Học có đủ vẫn bị hack như thường</h4>
<p>Điển hình như năm 2021 lê thê tận sang 2022 vẫn còn, <a href="https://en.wikipedia.org/wiki/Log4Shell">log4shell</a> là từ khóa đáng sợ nhất cho các doanh nghiệp lớn, nhỏ (đều code bằng Java), khi thư viện log phổ biến nhất của Java "log4j" dính lỗi bảo mật cho phép hacker có thể "làm đủ trò".
Lỗi này của ai để đi đổ lỗi? dù có hiểu đủ các lỗi bảo mật phổ biến
nhất, thì một cái bug "nhỏ" trong thư viện quá phổ biến này cũng đủ san bằng một
công ty. (PS: các hacker thường không muốn "san bằng công ty" trừ khi quá ghét, mà muốn bí mật nằm vùng trong đó rồi hưởng lợi thì hơn).</p>
<h4>Các lỗi bảo mật phổ biến nhất: SQLi, XSS, SSTI, CSRF</h4>
<p>Điều đáng sợ đầu tiên là hầu hết các lỗi bảo mật này đều được viết tắt, với
những chữ S, những chữ X, khiến cho chưa học thuộc từ khóa thì nhìn tên cũng đã sợ.</p>
<h5>SQLi - SQL injection</h5>
<p>Lỗi bảo mật phổ biến nhất thế giới từ 2005-2018 (số liệu chém gió mà chắc sẽ đúng), nguyên nhân
là do nối/format string mà không dùng ? hay %s đã ghi chú trong các thư viện xử lý SQL.</p>
<p>Never do this -- insecure! (format SQL string)</p>
<div class="highlight"><pre><span></span><code><span class="n">t</span> <span class="o">=</span> <span class="s1">'RHAT'</span>
<span class="n">cur</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="sa">f</span><span class="s2">"SELECT * FROM stocks WHERE symbol = '</span><span class="si">{</span><span class="n">t</span><span class="si">}</span><span class="s2">'"</span><span class="p">)</span>
</code></pre></div>
<p>Do this instead</p>
<div class="highlight"><pre><span></span><code><span class="n">t</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'RHAT'</span><span class="p">,)</span>
<span class="n">cur</span><span class="o">.</span><span class="n">execute</span><span class="p">(</span><span class="s1">'SELECT * FROM stocks WHERE symbol=?'</span><span class="p">,</span> <span class="n">t</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">cur</span><span class="o">.</span><span class="n">fetchone</span><span class="p">())</span>
</code></pre></div>
<p>(trích tài liệu <a href="https://docs.python.org/3.10/library/sqlite3.html#sqlite3-placeholders">python sqlite3</a> - <a href="https://www.psycopg.org/docs/usage.html#the-problem-with-the-query-parameters">postgres</a> cũng có tài liệu tương tự, 2022 DB nào cũng có hết).</p>
<p>Lỗ hổng này phổ biến tới mức công ty bảo mật hàng đầu cũng dính, tây cũng dính, tàu cũng dính,
để thấy dù đã sau hơn 15 năm lộng hành, ngành giáo dục thế giới vẫn chưa hoàn
thành nhiệm vụ. Lỗi này không từ ai, kể cả đại học, siêu học bằng cấp đầy mình mà ra,
đừng đổ tại học, vì học có dạy đâu mà biết.</p>
<h5>SSTI - Server side template injection</h5>
<p>Vẫn là "injection", tức nhét thêm 1 chút chút vào cái phần chính hợp lệ.</p>
<p>Ở SQLi thì inject thêm 1 phần câu SQL, ở "command injection" thì thêm 1 vài
dấu <code>;rm -rf /</code> đi kèm sau đầu vào được yêu cầu thì ở SSTI là thêm 1 chút (code)
vào chỗ mà phải nhập giá trị bình thường.</p>
<p>SSTI là việc hacker nhập vào một đoạn code, mà khi server thực hiện
render template (như django template, hay Jinja2)
sẽ chạy đoạn code này trên server. Về cơ bản khi dùng các template engine đều
đã xử lý SSTI bằng cách escape các ký tự "bất thường", nhưng vẫn không ít khi
các giá trị lọt vào không qua đường template render mà xử lý ngay như string trên python.</p>
<p>Ví dụ với fstring đầy uy lực theo <a href="https://semgrep.dev/docs/cheat-sheets/flask-xss/">semgrep</a>:</p>
<p>Bug:</p>
<div class="highlight"><pre><span></span><code><span class="n">render_template_string</span><span class="p">(</span><span class="sa">f</span><span class="s2">"<div></span><span class="si">{</span><span class="n">request</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"name"</span><span class="p">)</span><span class="si">}</span><span class="s2"></div>"</span><span class="p">)</span>
</code></pre></div>
<p>Fix:</p>
<div class="highlight"><pre><span></span><code><span class="n">render_template_string</span><span class="p">(</span><span class="s2">"<div>{{ name }}</div>"</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">args</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"name"</span><span class="p">))</span>
</code></pre></div>
<h5>XSS - Cross Site Scripting</h5>
<p>XSS thực sự khó hơn 2 cái lỗi trên đối với lập trình viên Python, bởi nó dùng JavaScript.
Nhưng ở mức khó bình thường, nên vẫn học được. Cơ chế cơ bản như sau:</p>
<ul>
<li>Trang web cho người dùng nhập vào (ví dụ username, info), và sau đó sẽ hiển thị giá trị đó ở 1 trang khác</li>
<li>Hacker nhập vào đoạn code javascript, phổ biến nhất là <code><script>alert(1);</script></code></li>
<li>Khi người dùng khác, hay nguy hiểm hơn là admin đọc nội dung này, nó sẽ chạy đoạn code JavaScript hacker đưa vào
trên máy họ. Đoạn code JavaScript thì có thể làm đủ trò, đơn giản nhất là gửi cookie của người dùng tới một trang khác để đánh cắp cookie này. Hacker sau đó lấy cookie này đăng nhập vào tài khoản người dùng, chiếm quyền.
Nguy hiểm nhất khi người bị hack là admin -> xong, hết bài.</li>
</ul>
<p>Dùng framework cũng không miễn nhiễm với XSS. Ví dụ trang chủ của Flask:</p>
<p>Bug:</p>
<div class="highlight"><pre><span></span><code><span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/index/<msg>"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">index</span><span class="p">(</span><span class="n">msg</span><span class="p">):</span>
<span class="k">return</span> <span class="s2">"Hello! "</span> <span class="o">+</span> <span class="n">msg</span>
</code></pre></div>
<p>yes, sorry!!! nhưng nếu trang web lưu giá trị này lại và hiển thị ở 1 chỗ khác,
và người khác xem, thì nó đã đủ cơ chế để hacker thực hiện XSS bằng cách nhập
code JavaScript vào msg.</p>
<p>Fix:</p>
<p>chỉ return render_template() hay jsonify(), không return string trực tiếp.</p>
<p><a href="https://docs.djangoproject.com/en/4.1/topics/security/#cross-site-scripting-xss-protection">Django doc</a> có ví dụ khi trong template ghi</p>
<p>Bug:</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">style</span> <span class="na">class</span><span class="o">=</span><span class="s">{{</span> <span class="na">var</span> <span class="err">}}</span><span class="p">></span><span class="o">...</span><span class="p"></</span><span class="nt">style</span><span class="p">></span>
</code></pre></div>
<p>Nếu hacker đưa được var với giá trị là <code>class1 onmouseover=javascript:func()</code>
thì code javascript này vẫn được chạy.</p>
<p>Fix: phải quote double quote -> <code>"{{ var }}"</code>.</p>
<p>Hay với link - tham khảo thêm tại <a href="https://flask.palletsprojects.com/en/2.2.x/security/#cross-site-scripting-xss">doc Flask</a>:</p>
<p>Bug:</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"{{ value }}"</span><span class="p">></span>click here<span class="p"></</span><span class="nt">a</span><span class="p">></span>
</code></pre></div>
<p>Hacker nhập vào render thành:</p>
<div class="highlight"><pre><span></span><code><span class="p"><</span><span class="nt">a</span> <span class="na">href</span><span class="o">=</span><span class="s">"javascript:alert('unsafe');"</span><span class="p">></span>click here<span class="p"></</span><span class="nt">a</span><span class="p">></span>
</code></pre></div>
<p>Fix: phải set <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy">CSP</a>.</p>
<h5>CSRF - Cross Site Request Forgery</h5>
<p>bài tập về nhà cho bạn đọc, tự học. PS tham khảo tại doc <a href="https://docs.djangoproject.com/en/4.1/ref/csrf/">Django</a>.</p>
<h5>JWT - JSON web tokens</h5>
<p><a href="https://pyjwt.readthedocs.io/en/latest/">JWT</a> không phải lỗi bảo mật, nhưng hiểu đầy đủ về nó là quan trọng do việc sử
dụng phổ biến của công nghệ này trong authen.</p>
<h5>Còn NHIỀU nữa</h5>
<p>Xem nhiều các lỗi bảo mật phổ biến tại <a href="https://owasp.org/www-project-top-ten/">OWASP</a></p>
<h5>Sysadmin, devops - command injection</h5>
<p>Các tool bash/python của sysadmin thường dính lỗi "command injection" khi cho phép người dùng nhập 1 phần câu
lệnh vào để chạy</p>
<p>Bug trên shell script:</p>
<div class="highlight"><pre><span></span><code><span class="nb">echo</span> <span class="s2">"Hello </span><span class="nv">$1</span><span class="s2">"</span>
</code></pre></div>
<p>Hacker nhập vào "hi; echo hello > hacked" là xong.</p>
<p>Hay dùng Python <a href="https://docs.python.org/3.10/library/subprocess.html#security-considerations">subprocess</a>:</p>
<p>Bug:</p>
<div class="highlight"><pre><span></span><code><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="sa">f</span><span class="s2">"echo hello </span><span class="si">{</span><span class="n">username</span><span class="si">}</span><span class="s2">"</span><span class="p">,</span> <span class="n">shell</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</code></pre></div>
<p>Fix:</p>
<div class="highlight"><pre><span></span><code><span class="n">subprocess</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="sa">f</span><span class="s2">"echo hello </span><span class="si">{</span><span class="n">username</span><span class="si">}</span><span class="s2">"</span><span class="o">.</span><span class="n">split</span><span class="p">(),</span> <span class="n">shell</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
</code></pre></div>
<p>Dùng os.system tương đương trường hợp bug trên.</p>
<h3>"Bóc phốt" những hiểu nhầm về CTF</h3>
<h4>Phải rất "giỏi máy tính" mới chơi được, phải biết code</h4>
<p>Giải CTF có giải khó giải dễ, có bài khó bài dễ, nhiều bài kéo đề vào tool là ra
kết quả, chưa kịp dùng não.
Nghề hack đã có từ ngày có máy tính, nên việc hack cũng có sẵn cả rổ tool để dùng,
vậy nên nhiều bài không cần phải code.
Có mục mới "OSINT", kỹ năng "tìm kiếm thông tin" cũng không cần dùng tới code,
giống như giải đố, tìm vị trí, tìm ai đó trên các trang mạng xã hội.
Tất nhiên để làm được nhiều bài khó hơn thì cần biết code if/for.</p>
<h4>Chơi pwn (binary exploitation) hay rev (reverse engineering) phải biết đọc assembly như đọc tiếng mẹ đẻ</h4>
<p>Tool rất quan trọng, và có nhiều tool rất xịn.
Các cầu thủ đá bóng đi toàn giày đinh loại cực xịn, các runner đi Nike airflow màu hồng phá kỷ lục thế giới,
các game thủ dùng bàn phím độ trễ siêu thấp, chuột có dăm ba nút phụ để combat cho nhanh, chả nghề nào chân trần phi vào quái thú để thất bại rồi ra nghĩa địa cả (trở về là còn tốt).</p>
<p>Các pwn thủ, rev thủ toàn dùng <a href="https://hex-rays.com/ida-pro/">IDA Pro</a> bản trả tiền, <a href="https://binary.ninja/">Ninja</a> hàng xịn, bấm một nút là biến asm thành code C ngay (ahuhu vẫn phải đọc code C), hàng free như Ghidra hay radare2 nhiều khi không xịn bằng, chứ đừng nói tới dump code Assembly rồi đọc chay.</p>
<h4>Chơi crypto phải giỏi toán</h4>
<p>Giỏi toán (mà phải đúng loại toán) đúng là có lợi, có thể giải nhiều bài khó,
nhưng crypto cũng có nhiều tool sẵn như <a href="https://github.com/RsaCtfTool/RsaCtfTool">RSACTFtool</a>, nhét đề vào viết thêm tí code là giải được cả mớ. Nhớ luyện hết theo <a href="https://cryptohack.org/challenges/rsa/">CryptoHack</a>, đọc cả blog vì có mấy lần đề KHÓ ra trúng <a href="https://blog.cryptohack.org/twitter-secrets">bài blog trên cryptohack</a>.</p>
<h4>Toàn là code C, C++, Assembly</h4>
<p>Mục pwn hay rev đa số là C/C++/asm, nhưng thi thoảng cũng có Python, Erlang, JavaScript.
Các mục khác (web/misc) sẽ chứa đủ loại ngôn ngữ chứ ít có C/C++/Asm.</p>
<h4>top 10 ctftime.org là giỏi nhất</h4>
<p>Đầu tiên là chuyện rất thú vị, 6 tháng đầu năm, top 10 là một bộ mặt khác hoàn toàn với
thời điểm đầu tháng 10 này. Cục diện thay đổi hoàn toàn, những top 10 vài tháng trước giờ
đã tụt xuống 20 30.</p>
<p>Thứ hai, ctftime.org chỉ là phần nổi của tảng băng (rất chìm), đâu phải hacker nào cũng rảnh mà đi thi, còn đang bận hack cả thế giới. Riêng có giải <a href="https://ctftime.org/event/1685">HTB Business</a> mới thấy các team security của các công ty ló mặt.</p>
<h4>Python Pickle vô dụng</h4>
<p>Một loại bài gần như giải nào cũng có, là lỗi bảo mật liên quan đến pickle. Nếu chơi cho vui thì nghe cũng được,
nhưng thứ không bảo mật thế giờ còn ai dùng? tìm mỏi mắt còn không thấy, đầu tư thời gian vào pickle chỉ để chơi CTF,
không có tác dụng trong thực tế... cho đến khi thấy nó: Bottle - một web framework rất phổ biến của Python, vẫn
dùng pickle khi get cookie, <a href="https://github.com/bottlepy/bottle/issues/900">issue mở từ 2016, vẫn chưa fix</a>.</p>
<h3>Những khó khăn</h3>
<h4>Lịch chơi dày đặc</h4>
<p>Bận rộn đi làm cả tuần, cuối tuần 2 ngày nghỉ lại chiến, tuần nào cũng thế, chưa
kịp ngấm giải này, học các kỹ thuật mới, thì đã tới giải khác. Chơi CTF có lẽ
là lý do blog này cả năm nay không có bài nào, không ai donate không phải là lý do =)</p>
<h4>writeup delay</h4>
<p>Các bài viết hướng dẫn (gọi là writeup) sau giải bị delay 1 tới vài tuần, do ban tổ chức thường
yêu cầu các top team viết rồi nộp để chứng minh mình không cheat. Và sau vài ba
tuần, thì đang bận chiến giải khác quên mất cả đề rồi.</p>
<h4>pwn</h4>
<p>Khó.</p>
<p>Pwn thường hay chỉ "binary exploitation", với người code Python hay thậm chí $language
bất kỳ (trừ C/Assambly), thì đây là cả một thế giới hoàn toàn mới, mới tới mức
đọc hướng dẫn cho newbie vẫn không hiểu gì. Từ cấu trúc file binary cho tới cách
làm stack overflow.
Có giải của SamSung chỉ ngồi làm tut pwn trong đề cho hướng dẫn, rất hay và chi tiết.
Thò tay vào làm mà vẫn khó lên khó xuống với <a href="https://n.pymi.vn/py3utf8.html">python2 và 3</a>.</p>
<p>Trong CTF kiểu Jeopady, web và pwn là hai mục có tính "hack" nhất.</p>
<h4>Khó</h4>
<ul>
<li>0CTF</li>
<li>3DCTF</li>
<li>PPP</li>
<li>DEFCON</li>
</ul>
<p>hai giải đầu của Trung Quốc, các hacker Trung Quốc thì khét tiếng khỏi nói rồi.
PPP top 1 ctftime.org nhiều năm. DEFCON lừng danh blackhat.</p>
<p>Các giải này chỉ vào đọc đề rồi hì hục cả 2 ngày vẫn không ra gì. Sợ.</p>
<h3>Mục tiêu kế tiếp</h3>
<p>Sau năm đầu, cá nhân mình chơi tất cả các thể loại, từ misc, forensics qua crypto, tới
rev, sang web, thi thoảng đề dễ làm cả pwn hay chạy autopwn để tự giải.
Sang năm phấn đấu theo web và pwn cho nó thực chiến.</p>
<h3>Bài học rút ra và hành động của chúng ta</h3>
<ul>
<li>Biển học vô biên, bị điên thì học. Cứ ngỡ mình Python ngon ăn lắm, mà nhiều bài Pyjail (phá ra khỏi chương trình Python đã bị giới hạn các tính năng để chạy lệnh thoải mái - RCE) vẫn không giải được. Bước sang web hay binary thì
ôi thôi cả một chân trời mới. Lại newbie lại từ đầu.</li>
<li>Kiểu <a href="https://n.pymi.vn/byt351.html">byte</a> ngày ngày đi code, 10 năm không bao giờ dùng thì đi chơi CTF giải nào cũng dùng.</li>
<li>Khi đi code, hãy sử dụng các công cụ phát hiện lỗi bảo mật trong code như semgrep, bandit, snyk, ...</li>
<li>Các công ty hãy cho nghỉ làm 1 ngày để các dev tự tập hack website của chính mình, training hand-on hack các lỗ hổng
bảo mật hay gặp nhất, lập team CTF chơi mục web hàng tuần.</li>
<li>Team PyMi chơi ít giải đi, mà chất hơn.</li>
</ul>
<h3>Bắt đầu từ đâu?</h3>
<p>Tài liệu vô biên, search là ra cả đống, tham khảo:</p>
<ul>
<li><a href="https://n.pymi.vn/byt351.html">python cho hack với byte</a></li>
<li><a href="http://pp.pymi.vn/article/ctf/">PyMi SNYK 2021 writeup</a></li>
<li><a href="https://github.com/pymivn/ctf">repo của team CTF PyMi</a></li>
</ul>
<h3>Kết luận</h3>
<p>Cám ơn các anh em team CTF PyMi đã cùng chiến đấu suốt 1 năm trời, nhiều anh em
vẫn còn chưa biết mặt. Hy vọng năm thứ 2 sẽ gặp nhau được ít nhất 1 lần, không thì cũng
2 lần trở lên.</p>
<p>To another CTFking year.</p>
<p>Hết.</p>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>.</p>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
</ul>Chinh phục Advent of Code 2021 bằng Rust2022-01-04T00:00:00+07:002022-01-04T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2022-01-04:/article/aoc2021/<p>chiến game coding hot nhất tháng 12 mỗi năm, bằng ngôn ngữ hot nhất trên các bảng xếp hạng: Rust.</p><p>Advent of code (viết tắt AoC) là một cuộc thi code hàng năm kéo dài suốt 25 ngày trong tháng 12.
Mỗi ngày, lúc 12h trưa giờ Việt Nam (UTC+7), một bài toán đố nhỏ gồm 2 phần dưới dạng
câu chuyện "giải cứu đêm noel" sẽ được mở. Người chơi giải được phần 1 mới được chơi phần 2, giải
xong mỗi phần sẽ nhận được 1 sao. 12 giờ trưa ngày 25/12 sẽ ra bài cuối cùng, chỉ có 1 phần,
người chơi đủ 49 sao sẽ được tặng 1 sao và kết thúc trò chơi.</p>
<p><a href="https://www.lexico.com/definition/advent">Advent</a> /ˈadvɛnt/ trong tiếng Anh nghĩa là</p>
<blockquote>
<p>The first season of the Church year, leading up to Christmas and including the four preceding Sundays.</p>
<p>(tôn giáo) kỳ trông đợi, mùa vọng (bốn tuần lễ trước ngày giáng sinh của Chúa) - theo <a href="http://tratu.soha.vn/dict/en_vn/Advent">tratu.soha.vn</a></p>
</blockquote>
<p>Trang web <a href="https://adventofcode.com/">adventofcode.com</a> được tạo bởi <a href="http://was.tl/">Eric Wastl</a>
xuất hiện lần đầu vào <a href="https://adventofcode.com/2015">năm 2015</a>, ngày càng phổ biến và được cộng đồng
lập trình viên toàn cầu mong chờ mỗi tháng 12.</p>
<p><a href="https://www.youtube.com/watch?v=QUwxKWT6m7U"><img alt="Back to December" src="https://img.youtube.com/vi/QUwxKWT6m7U/0.jpg"></a>
<center>🎥 Taylor Swift - Back To December</center></p>
<p>Để thêm phần gay cấn, AoC có <a href="https://adventofcode.com/2021/leaderboard">bảng xếp hạng toàn cầu leaderboard</a>, người giải đầu tiên mỗi phần sẽ được 100 điểm và giảm dần. Người chơi cũng có thể tự tạo bảng xếp hạng riêng,
giúp các cộng đồng có thể tự chơi với nhóm của mình. PyMi tổ chức AoC với giải
thưởng hấp dẫn từ 2020, bảng xếp hạng tại <a href="https://adventofcode.com/2021/leaderboard/private">đây</a> nhập mã <code>416592-f7938347</code></p>
<p>Trò chơi thu hút cả những lập trình viên nổi tiếng thế giới như:</p>
<ul>
<li><a href="http://norvig.com/">Peter Norvig</a> - giáo sư đầu ngành AI, giám đốc nghiên
cứu của Google. Năm 2020, <a href="https://github.com/norvig/pytudes/blob/main/ipynb/Advent-2021.ipynb">2021</a> giải gần như tất cả các bài bằng Python trong 1 file Jupyter Notebook.</li>
<li>José Valim - tác giả ngôn ngữ lập trình <a href="https://elixir-lang.org/">Elixir</a>, <a href="https://www.twitch.tv/collections/k_DLnk2tvBa-fQ">livestream trên twitch</a> năm nay giải AoC với Elixir LiveBook (tương đương Jupyter Notebook).</li>
<li><a href="https://en.wikipedia.org/wiki/George_Hotz">geohot</a> - hacker, người đầu tiên jailbreak iOS, hack PS3, ... https://www.youtube.com/watch?v=OxDp11u-GUo</li>
</ul>
<p>Mỗi người chơi đến với AdventOfCode có một lý do khác nhau: có người để đua top
giật giải, có người dùng để học ngôn ngữ lập trình mới, phương pháp lập trình
mới (như Test Drive Development - TDD), người dùng để giải trí, thoát khỏi công việc nhàm chán
hàng ngày, người lại dùng để "ôn tập hàng năm" các kiến thức cấu trúc dữ liệu giải thuật "căn bản"
từng học trên giấy mà 10 năm đi làm
chưa dùng bao giờ như thuật toán Dijkstra, Priority queue, binary tree...</p>
<p>AoC có <a href="https://www.reddit.com/r/adventofcode/">cộng đồng Reddit</a> đông đảo với
hơn 30 nghìn thành viên, nơi chia sẻ code bài giải, những video
thực hiện hiển thị, game hóa bài toán/bài giải và nhiều điều hấp dẫn khác.</p>
<p>Là một người chơi AoC lâu năm <a href="https://github.com/hvnsweeting/adventofcode">từ 2018</a>,
với 3 năm liền dùng Elixir, năm nay tôi quyết định chơi bằng Rust.</p>
<h2>Rust - ngôn ngữ lập trình tham lam: Performance, Reliability, Productivity - chọn cả 3!</h2>
<p><img alt="Rust" src="https://www.rust-lang.org/logos/rust-logo-blk.svg"></p>
<p>Ngôn ngữ hiện đại (từ 2010), <a href="https://stackoverflow.blog/2020/01/20/what-is-rust-and-why-is-it-so-popular/">6 năm liền</a>
được bình chọn là ngôn ngữ được <a href="https://insights.stackoverflow.com/survey/2021#section-most-loved-dreaded-and-wanted-programming-scripting-and-markup-languages">yêu thích nhất theo khảo sát của StackOverflow</a>.</p>
<p>Rust có thể dùng để thay cho:</p>
<ul>
<li>C++: <a href="https://hacks.mozilla.org/2016/07/shipping-rust-in-firefox/">https://hacks.mozilla.org/2016/07/shipping-rust-in-firefox/</a></li>
<li>Go: <a href="https://blog.discord.com/why-discord-is-switching-from-go-to-rust-a190bbca2b1f">https://blog.discord.com/why-discord-is-switching-from-go-to-rust-a190bbca2b1f</a></li>
<li>Python: <a href="https://dropbox.tech/infrastructure/rewriting-the-heart-of-our-sync-engine">https://dropbox.tech/infrastructure/rewriting-the-heart-of-our-sync-engine</a></li>
<li>Ruby: <a href="https://deliveroo.engineering/2019/02/14/moving-from-ruby-to-rust.html">https://deliveroo.engineering/2019/02/14/moving-from-ruby-to-rust.html</a></li>
<li>NodeJS: <a href="https://www.rust-lang.org/static/pdfs/Rust-npm-Whitepaper.pdf">https://www.rust-lang.org/static/pdfs/Rust-npm-Whitepaper.pdf</a></li>
</ul>
<p>Rust thường được dùng để thay C++, C, được chọn khi một phần của hệ
thống cần tốc độ tối đa. Rust luôn đứng top đầu về tốc độ trong các bảng xếp hạng
benchmark [<a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/box-plot-summary-charts.html">1</a>] [<a href="https://www.techempower.com/benchmarks/">2</a>].</p>
<h3>Các phần mềm phổ biến viết bằng Rust</h3>
<ul>
<li><a href="https://github.com/servo/servo">Servo</a> browser engine trong trình duyệt FireFox</li>
<li><a href="https://github.com/BurntSushi/ripgrep">ripgrep (rg)</a> - thay cho grep command line, nhanh hơn, xịn hơn</li>
<li>một phần của NodeJS npm: <a href="https://www.rust-lang.org/static/pdfs/Rust-npm-Whitepaper.pdf">https://www.rust-lang.org/static/pdfs/Rust-npm-Whitepaper.pdf</a></li>
<li>FireCracker VM (để chạy các container phía dưới AWS Lambda) <a href="https://github.com/firecracker-microvm/firecracker">https://github.com/firecracker-microvm/firecracker</a></li>
<li>Linux kernel hỗ trợ Rust bên cạnh C <a href="https://lore.kernel.org/lkml/20211206140313.5653-1-ojeda@kernel.org/">https://lore.kernel.org/lkml/20211206140313.5653-1-ojeda@kernel.org/</a></li>
</ul>
<p>Bài viết sẽ giới thiệu vừa đủ các khái niệm của Rust đã dùng để chinh phục
50 bài toán đố của AoC 2021.</p>
<h3>Cài đặt</h3>
<p>Chạy lệnh ghi trên trang <a href="https://rustup.rs/">rustup.rs</a> để cài:</p>
<div class="highlight"><pre><span></span><code>curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
</code></pre></div>
<p>Mặc dù các hệ điều hành đều có package manager để cài Rust như Ubuntu <code>apt</code>, Fedora <code>yum</code>, hay MacOS <code>homebrew</code>,
<code>rustup</code> là công cụ được khuyên dùng chính thức vì nó có khả năng cài thêm các phần liên quan
đến việc code Rust như: document, auto-complete engine,... mà thường không có khi
cài qua package manager.</p>
<p>Code Rust có thể dùng <a href="https://www.jetbrains.com/rust/">IntelliJ</a>, hay <a href="https://marketplace.visualstudio.com/items?itemName=rust-lang.rust">VSCode</a> để có hỗ trợ auto-complete, các editor
khác sẽ cần tự cài đặt thêm thủ công hơn.</p>
<h3>Tài liệu</h3>
<ul>
<li>“the book” The Rust Programming Language: <a href="https://doc.rust-lang.org/book/">https://doc.rust-lang.org/book/</a></li>
<li>Nửa giờ học Rust <a href="https://fasterthanli.me/articles/a-half-hour-to-learn-rust">https://fasterthanli.me/articles/a-half-hour-to-learn-rust</a></li>
<li>Rust & WebAssembly <a href="https://rustwasm.github.io/docs/book/">https://rustwasm.github.io/docs/book/</a></li>
<li><a href="https://rust-cli.github.io/book/index.html">Command Line Applications in Rust</a></li>
<li>Và nhiều tài liệu khác tại <a href="https://www.rust-lang.org/learn">https://www.rust-lang.org/learn</a></li>
</ul>
<h3>Cách học</h3>
<p>Gõ <code>rustup doc</code>, đọc 4 chương đầu + chương 8 trong
<a href="https://doc.rust-lang.org/book/">https://doc.rust-lang.org/book/</a>
là đủ để code.</p>
<h3>Build & run</h3>
<p>Rust compile code thành file binary rồi chạy.
Rust compiler có câu lệnh <code>rustc</code>, gõ <code>rustc file.rs</code> để compile, rồi chạy <code>./file</code>.
Nhưng Rust có kèm "cargo", package manager của Rust, như pip của Python, như
npm của nodejs... cargo hỗ trợ mọi tác vụ cần để code 1 Rust project.</p>
<p>Để tạo 1 Rust project, gõ:</p>
<div class="highlight"><pre><span></span><code>$ cargo new project-name
Created binary <span class="o">(</span>application<span class="o">)</span> <span class="sb">`</span>project-name<span class="sb">`</span> package
$ <span class="nb">cd</span> project-name/
</code></pre></div>
<p>lệnh này sinh ra file config cho cargo <code>Cargo.toml</code> và code 1 chương trình hello world
nằm trong src/main.rs</p>
<div class="highlight"><pre><span></span><code>./Cargo.toml
./src
./src/main.rs
</code></pre></div>
<p>rồi chạy:</p>
<div class="highlight"><pre><span></span><code>$ cargo run
Compiling project-name v0.1.0 <span class="o">(</span>/tmp/project-name<span class="o">)</span>
Finished dev <span class="o">[</span>unoptimized + debuginfo<span class="o">]</span> target<span class="o">(</span>s<span class="o">)</span> <span class="k">in</span> <span class="m">0</span>.60s
Running <span class="sb">`</span>target/debug/project-name<span class="sb">`</span>
Hello, world!
</code></pre></div>
<p>Để chạy test, gõ</p>
<div class="highlight"><pre><span></span><code>$ cargo <span class="nb">test</span>
</code></pre></div>
<p>Để format code, gõ</p>
<div class="highlight"><pre><span></span><code>$ cargo fmt
</code></pre></div>
<h3>Hello, World!</h3>
<p>Gõ lệnh <code>cargo new</code> đã tự tạo ra code hello world, trong file src/main.rs:</p>
<div class="highlight"><pre><span></span><code><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"Hello, world!"</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Code của chương trình Rust nằm trong file <code>main.rs</code> và chạy từ function <code>main</code>.
<code>fn main() {...}</code> định nghĩa 1 function tên <code>main</code>, sử dụng từ khóa <code>fn</code>, thân
function nằm trong cặp <code>{}</code>.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"Hello, world!"</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>không khác Python là mấy:</p>
<div class="highlight"><pre><span></span><code>print("Hello, world!")
</code></pre></div>
<p><code>println!()</code> trông như 1 function, nhưng trong Rust, khi thấy dấu <code>!</code> thì đó là
biểu diễn của 1 macro.</p>
<ul>
<li>macro <strong>sinh ra code</strong> thực hiện việc "in ra màn hình"</li>
<li>Python <code>print</code> function thực hiện việc "in ra màn hình"</li>
</ul>
<p>Mọi câu lệnh trong Rust kết thúc bằng dấu chấm phẩy <code>;</code></p>
<h3><a href="https://projecteuler.net/problem=1">ProjectEuler.net problem 1</a></h3>
<blockquote>
<p>Tính tổng các số tự nhiên nhỏ hơn 1000 chia hết cho 3 hoặc 5.</p>
</blockquote>
<p>Để tạo 1 biến, gõ <code>let tên = giá trị;</code></p>
<div class="highlight"><pre><span></span><code><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="mi">1000</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">i</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"Kết quả: {}"</span><span class="p">,</span><span class="w"> </span><span class="n">result</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Cũng không quá khác code Python:</p>
<div class="highlight"><pre><span></span><code><span class="n">result</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1000</span><span class="p">):</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">result</span> <span class="o">+=</span> <span class="n">i</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Kết quả </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
</code></pre></div>
<p>Rust giống C/C++/Java, dùng <code>{}</code>làm khối lệnh nhóm các câu lệnh trong if/for/function,
khác với Python dùng indentation (thụt vào 4 dấu space).
Biến trong Rust mặc định không
thay đổi được sau khi tạo, phải thêm từ khóa <code>mut</code> (viết tắt của mutable): <code>let mut result = 0;</code> để thay đổi <code>result</code>.
<code>println!</code> nhận 1 string, với <code>{}</code> để format, rồi tới các giá trị theo sau.</p>
<p>Rust có kiểu vector <code>Vec</code>, tương tự như Python <code>list</code>:</p>
<div class="highlight"><pre><span></span><code><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">vec</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[];</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="mi">1000</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="o">%</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">vec</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="n">i</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">result</span>: <span class="kt">i32</span> <span class="o">=</span><span class="w"> </span><span class="n">vec</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">sum</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span><span class="w"> </span><span class="n">result</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>nhưng...</p>
<p>mặc dù có không ít khái niệm tương tự Python, hay syntax rút gọn trông cũng gần
giống, thì Rust lại là một con quái vật hoàn toàn khác, khác Python nhiều hơn
là giống.</p>
<h4>Giống Python</h4>
<p>Rust có các kiểu dữ liệu built-in tương tự Python:</p>
<table>
<thead>
<tr>
<th>Python</th>
<th>Rust</th>
<th>Chú thích cho Rust</th>
</tr>
</thead>
<tbody>
<tr>
<td>list</td>
<td>Vec</td>
<td>vector</td>
</tr>
<tr>
<td>dict</td>
<td>HashMap</td>
<td></td>
</tr>
<tr>
<td>set</td>
<td>HashSet</td>
<td></td>
</tr>
<tr>
<td>tuple</td>
<td>(a, b)</td>
<td>dùng cú pháp, không có kiểu ở dạng tên</td>
</tr>
<tr>
<td>int</td>
<td>i64, i32..</td>
<td>có u64 u32... cho kiểu không âm, usize cho kích thước</td>
</tr>
<tr>
<td>float</td>
<td>f64, f32</td>
<td></td>
</tr>
<tr>
<td>bool</td>
<td>bool</td>
<td></td>
</tr>
<tr>
<td>str</td>
<td>&str, String</td>
<td></td>
</tr>
<tr>
<td>KHÔNG CÓ</td>
<td>char</td>
<td></td>
</tr>
<tr>
<td>None</td>
<td>None</td>
<td>giá trị</td>
</tr>
</tbody>
</table>
<h5>Tuple</h5>
<div class="highlight"><pre><span></span><code><span class="kd">let</span><span class="w"> </span><span class="p">(</span><span class="n">name</span><span class="p">,</span><span class="w"> </span><span class="n">age</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">(</span><span class="s">"PYMI"</span><span class="p">,</span><span class="w"> </span><span class="mi">6</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<h5>Vector</h5>
<p>Vector tương tự như Python list. Lặp qua các phần tử của 1 vector:</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span><span class="w"> </span><span class="n">vec</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">];</span><span class="w"></span>
<span class="k">for</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">vec</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span><span class="w"> </span><span class="n">n</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<h5>HashSet</h5>
<div class="highlight"><pre><span></span><code><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">collections</span>::<span class="n">HashSet</span><span class="p">;</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">set</span>: <span class="nc">HashSet</span><span class="o"><</span><span class="kt">i32</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">HashSet</span>::<span class="n">from</span><span class="p">([</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">]);</span><span class="w"></span>
<span class="w"> </span><span class="n">set</span><span class="p">.</span><span class="n">extend</span><span class="p">([</span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">]);</span><span class="w"></span>
<span class="w"> </span><span class="fm">dbg!</span><span class="p">(</span><span class="o">&</span><span class="n">set</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">[</span><span class="n">src</span><span class="o">/</span><span class="n">main</span><span class="p">.</span><span class="n">rs</span>:<span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="o">&</span><span class="n">set</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<ul>
<li><code>dbg!</code> để <strong>print debug</strong> trong Rust.</li>
</ul>
<h5>HashMap</h5>
<p>HashMap key không có thứ tự, tương tự với Python dict trước 3.6,
cú pháp import <code>use std::collections::HashMap;</code>
như Python <code>from collections import Counter</code>:</p>
<div class="highlight"><pre><span></span><code><span class="k">use</span><span class="w"> </span><span class="n">std</span>::<span class="n">collections</span>::<span class="n">HashMap</span><span class="p">;</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">double</span><span class="p">(</span><span class="n">x</span>: <span class="kt">i32</span><span class="p">)</span><span class="w"> </span>-> <span class="kt">i32</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">d</span>: <span class="nc">HashMap</span><span class="o"><&</span><span class="kt">str</span><span class="p">,</span><span class="w"> </span><span class="kt">i32</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">HashMap</span>::<span class="n">from</span><span class="p">([(</span><span class="s">"Hà Nội"</span><span class="p">,</span><span class="w"> </span><span class="mi">1_612</span><span class="p">),</span><span class="w"> </span><span class="p">(</span><span class="s">"Cà Mau"</span><span class="p">,</span><span class="w"> </span><span class="mi">967</span><span class="p">)]);</span><span class="w"></span>
<span class="w"> </span><span class="n">d</span><span class="p">.</span><span class="n">insert</span><span class="p">(</span><span class="s">"TP HCM"</span><span class="p">,</span><span class="w"> </span><span class="mi">687</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">k</span><span class="p">,</span><span class="w"> </span><span class="n">v</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">d</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"{}: {}"</span><span class="p">,</span><span class="w"> </span><span class="n">k</span><span class="p">,</span><span class="w"> </span><span class="n">double</span><span class="p">(</span><span class="n">v</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="c1">//Hà Nội: 1612</span>
<span class="c1">//TP HCM: 687</span>
<span class="c1">//Cà Mau: 967</span>
</code></pre></div>
<p>Cú pháp type của function tương tự Python:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">double</span><span class="p">(</span><span class="n">x</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span>
<span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">d</span><span class="p">:</span> <span class="nb">dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="nb">int</span><span class="p">]</span> <span class="o">=</span> <span class="nb">dict</span><span class="p">([(</span><span class="s2">"Hà Nội"</span><span class="p">,</span> <span class="mi">1_612</span><span class="p">),</span> <span class="p">(</span><span class="s2">"Cà Mau"</span><span class="p">,</span> <span class="mi">967</span><span class="p">)])</span>
<span class="n">d</span><span class="p">[</span><span class="s2">"TP HCM"</span><span class="p">]</span> <span class="o">=</span> <span class="mi">687</span>
<span class="k">for</span> <span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">v</span><span class="p">)</span> <span class="ow">in</span> <span class="n">d</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="si">{}</span><span class="s2">: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">k</span><span class="p">,</span> <span class="n">double</span><span class="p">(</span><span class="n">v</span><span class="p">)))</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>Khác với đoạn code giải Project Euler 1, code trên có khai báo type
cho các biến. Rust không bắt buộc phải khai báo type khi nó có thể tự
suy luận được,
vì vậy đa số code không cần ghi type, khi nào cần, Rust compiler sẽ thông báo.</p>
<h3>Giải <a href="https://adventofcode.com/2021/day/1">ngày 1</a></h3>
<p>Nhìn chung các bài trong 7-8 ngày đầu tiên thường dễ, độ khó tăng dần về sau,
đặc biệt là tuần cuối cùng.</p>
<blockquote>
<p>How many measurements are larger than the previous measurement?</p>
</blockquote>
<p>Cho một dãy số tự nhiên (độ sâu của tàu ngầm) tăng giảm tùy ý.
Có bao nhiêu lần độ sâu tăng so với lần trước. Ví dụ:</p>
<div class="highlight"><pre><span></span><code><span class="mf">199</span> <span class="p">(</span><span class="n">N</span><span class="o">/</span><span class="n">A</span> <span class="o">-</span> <span class="n">no</span> <span class="n">previous</span> <span class="n">measurement</span><span class="p">)</span>
<span class="mf">200</span> <span class="p">(</span><span class="n">increased</span><span class="p">)</span>
<span class="mf">208</span> <span class="p">(</span><span class="n">increased</span><span class="p">)</span>
<span class="mf">210</span> <span class="p">(</span><span class="n">increased</span><span class="p">)</span>
<span class="mf">200</span> <span class="p">(</span><span class="n">decreased</span><span class="p">)</span>
<span class="mf">207</span> <span class="p">(</span><span class="n">increased</span><span class="p">)</span>
<span class="mf">240</span> <span class="p">(</span><span class="n">increased</span><span class="p">)</span>
<span class="mf">269</span> <span class="p">(</span><span class="n">increased</span><span class="p">)</span>
<span class="mf">260</span> <span class="p">(</span><span class="n">decreased</span><span class="p">)</span>
<span class="mf">263</span> <span class="p">(</span><span class="n">increased</span><span class="p">)</span>
</code></pre></div>
<p>Phần chú thích trong <code>()</code> là hướng dẫn, đề chỉ cho 1 file chứa các số, mỗi số 1 dòng.
Ví dụ trên có 7 lần tăng.</p>
<p>Các bước làm:</p>
<ul>
<li>đọc file input vào thành các dòng chứa các string</li>
<li>biến kiểu string thành integer (số nguyên)</li>
<li>lặp qua các giá trị, đếm số lần giá trị sau lớn hơn giá trị trước.</li>
</ul>
<p>Bài giải, theo kiểu dùng vòng lặp:</p>
<div class="highlight"><pre><span></span><code><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s">"199</span>
<span class="s">200</span>
<span class="s">208</span>
<span class="s">210</span>
<span class="s">200</span>
<span class="s">207</span>
<span class="s">240</span>
<span class="s">269</span>
<span class="s">260</span>
<span class="s">263"</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">lines</span>: <span class="nb">Vec</span><span class="o"><&</span><span class="kt">str</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">lines</span><span class="p">().</span><span class="n">collect</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">increases_count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">prev</span>: <span class="kp">&</span><span class="kt">str</span> <span class="o">=</span><span class="w"> </span><span class="n">lines</span><span class="p">[</span><span class="mi">0</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">line</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="o">&</span><span class="n">lines</span><span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="p">]</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">n</span>: <span class="kt">i32</span> <span class="o">=</span><span class="w"> </span><span class="n">line</span><span class="p">.</span><span class="n">parse</span><span class="p">().</span><span class="n">unwrap</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">p</span>: <span class="kt">i32</span> <span class="o">=</span><span class="w"> </span><span class="n">prev</span><span class="p">.</span><span class="n">parse</span><span class="p">().</span><span class="n">unwrap</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">p</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">increases_count</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">prev</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">line</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span><span class="w"> </span><span class="n">increases_count</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Người chơi có thể dùng <code>std::fs::read_to_string</code> để đọc từ file, nhưng ngày đầu
tiên dùng Rust, với một bài khởi động đơn giản thế này thì phần cản trở tốc độ giải cũng chính là
Rust. Bỏ qua việc đọc file trong lúc gấp gáp này hoàn toàn chấp nhận được.
Một vài điểm chú ý:</p>
<ul>
<li><code>line.parse()</code> để parse string thành integer. Rust tự biết parse thành kiểu
gì do vế trái khai báo kiểu cho kết quả <code>let n: i32</code>. <code>parse</code> không trả ngay về
số mà trả về kiểu <code>Result</code>, Result chứa số kiểu i32, hoặc chứa Error
nếu không parse được. <code>.unwrap()</code> để lấy ra giá trị <code>i32</code> hoặc panic dừng chương
trình luôn nếu parse gặp lỗi.</li>
<li>Python chỉ có kiểu <code>int</code> duy nhất cho integer, trong Rust có nhiều kiểu số: <code>i32</code>
cho kiểu integer với kích thước 32 bit, giá trị từ <code>-2**31</code> tới <code>2**31-1</code> (-2147483648..=2147483647), tương tự
với <code>i64</code>. Ngoài ra còn có kiểu <code>u32</code> (unsigned int), chỉ chứa số không âm, với
<code>u32</code> có giá trị từ <code>0</code> tới <code>2**32-1</code> (<code>0..=4294967295</code>), tương tự cho <code>u64</code>.</li>
<li><code>&str</code> là 1 trong 2 kiểu string hay dùng trong Rust, kiểu còn lại là <code>String</code>,
ngoài ra Rust có nhiều kiểu string khác dùng trong các trường hợp riêng biệt.</li>
<li><code>s.lines()</code> không trả ngay về một <code>Vec<&str></code>, nó trả về kiểu <code>Lines</code>. Để biến
thành kiểu <code>Vec</code>, dùng <code>collect()</code>. Chú ý vế trái phải khai báo kiểu do Rust
không thể tự suy ở đây vì người dùng có thể gọi <code>collect()</code> mà nhận được nhiều
kiểu khác nhau như Vec, HashSet, ... tùy ý.</li>
<li><code>&lines[1..]</code>, Vector có slice như Python, thay cú pháp <code>[a:b]</code> bằng <code>[a..b]</code> và
không có index âm (như -1).</li>
</ul>
<p>Cách trên không được coi là code theo kiểu Rust, mà giống viết code C/Python dịch sang
Rust hơn. Một phiên bản khác sử dụng <code>iterator</code> mang phong cách functional programming,
phải đến ngày thứ 10 trở đi, hay đọc xong <a href="https://doc.rust-lang.org/stable/book/ch13-00-functional-features.html">chương 13 trong "The Rust Book"</a> mới quen được kiểu này:</p>
<div class="highlight"><pre><span></span><code><span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span>::<span class="n">fs</span>::<span class="n">read_to_string</span><span class="p">(</span><span class="s">"input01"</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">lines</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">i32</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">lines</span><span class="p">().</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="n">i</span><span class="o">|</span><span class="w"> </span><span class="n">i</span><span class="p">.</span><span class="n">parse</span><span class="p">().</span><span class="n">unwrap</span><span class="p">()).</span><span class="n">collect</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">increases_count</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">lines</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">zip</span><span class="p">(</span><span class="o">&</span><span class="n">lines</span><span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="p">])</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">filter</span><span class="p">(</span><span class="o">|</span><span class="p">(</span><span class="n">prev</span><span class="p">,</span><span class="w"> </span><span class="n">next</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">next</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">prev</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">count</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="fm">println!</span><span class="p">(</span><span class="s">"{}"</span><span class="p">,</span><span class="w"> </span><span class="n">increases_count</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Ý tưởng khác một chút, thay vì duyệt qua dãy số, ta duyệt qua 2 dãy cùng 1 lúc
với <code>zip</code>, dãy thứ 2 bắt đầu từ phần tử index 1, và filter (lọc)
ra các cặp mà có giá trị sau lớn hơn giá trị trước, rồi đếm.</p>
<p><code>|(prev, next)| next > prev</code> là một closure, giống như Python lambda nhưng viết
bao nhiêu dòng cũng được. Code này tương tự Python <code>lambda t: t[1] > t[0]</code>.</p>
<p>Sau khi nhập kết quả giải xong phần 1, đề phần 2 hiện ra yêu cầu thay vì đếm số
sau lớn hơn số trước thì đếm tổng 3 số sau lớn hơn tổng 3 số trước.</p>
<div class="highlight"><pre><span></span><code><span class="mf">1</span>
<span class="mf">2</span>
<span class="mf">3</span>
<span class="mf">4</span>
</code></pre></div>
<p>thì có 2+3+4 > 1+2+3.</p>
<p>Tạo 1 list mới chứa tổng của 3 số liên tiếp, sau đó dùng list
đó làm đầu vào cho code của phần 1.</p>
<div class="highlight"><pre><span></span><code><span class="kd">let</span><span class="w"> </span><span class="n">lines</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">i32</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">lines</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">zip</span><span class="p">(</span><span class="o">&</span><span class="n">lines</span><span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="p">])</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">zip</span><span class="p">(</span><span class="o">&</span><span class="n">lines</span><span class="p">[</span><span class="mi">2</span><span class="o">..</span><span class="p">])</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">map</span><span class="p">(</span><span class="o">|</span><span class="p">((</span><span class="n">x1</span><span class="p">,</span><span class="w"> </span><span class="n">x2</span><span class="p">),</span><span class="w"> </span><span class="n">x3</span><span class="p">)</span><span class="o">|</span><span class="w"> </span><span class="n">x1</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">x2</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">x3</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">.</span><span class="n">collect</span><span class="p">();</span><span class="w"></span>
</code></pre></div>
<p>ở đây một lần nữa dùng zip để duyệt qua 3 dãy số cùng lúc.</p>
<h3>Giải <a href="https://adventofcode.com/2021/day/3">ngày 3</a></h3>
<p>Tính năng lượng tiêu thụ của tàu dựa trên báo cáo</p>
<div class="highlight"><pre><span></span><code><span class="mf">00100</span>
<span class="mf">11110</span>
<span class="mf">10110</span>
<span class="mf">10111</span>
<span class="mf">10101</span>
<span class="mf">01111</span>
<span class="mf">00111</span>
<span class="mf">11100</span>
<span class="mf">10000</span>
<span class="mf">11001</span>
<span class="mf">00010</span>
<span class="mf">01010</span>
</code></pre></div>
<p>Cách tính: <code>power = epsilon rate * gamma rate</code>.
Với gamma rate là các bit xuất hiện nhiều nhất ở mỗi cột trong tất cả các số.
Và epsilon rate là các bit xuất hiện ít nhất ở mỗi cột.
Ví dụ trên: cột 1 có nhiều số 1 nhất, cột 2 có nhiều số 0 nhất, ...
sau 5 cột ta có <code>10110</code>, đổi ra hệ cơ số 10 được giá trị 22.</p>
<p>Giải phần này, chỉ cần làm đúng như các bước mà đề bài mô tả, lấy các giá trị
theo từng cột, đếm giá trị nào nhiều nhất rồi cho vào 1 list, cuối cùng biến
thành số.</p>
<div class="highlight"><pre><span></span><code><span class="k">fn</span> <span class="nf">day02</span><span class="p">()</span><span class="w"> </span>-> <span class="kt">i32</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">std</span>::<span class="n">fs</span>::<span class="n">read_to_string</span><span class="p">(</span><span class="s">"input02"</span><span class="p">).</span><span class="n">unwrap</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">gamma_bits</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">_</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[];</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">epsilon_bits</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">_</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[];</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">lines</span>: <span class="nb">Vec</span><span class="o"><&</span><span class="kt">str</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="p">.</span><span class="n">lines</span><span class="p">().</span><span class="n">collect</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">idx</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="mi">0</span><span class="o">..</span><span class="n">lines</span><span class="p">[</span><span class="mi">0</span><span class="p">].</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">one</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">zero</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">line</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">lines</span><span class="p">.</span><span class="n">iter</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">chars</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">char</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">line</span><span class="p">.</span><span class="n">chars</span><span class="p">().</span><span class="n">collect</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">chars</span><span class="p">[</span><span class="n">idx</span><span class="p">]</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="sc">'0'</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">zero</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">one</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">one</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="n">zero</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">gamma_bits</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">epsilon_bits</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">gamma_bits</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">epsilon_bits</span><span class="p">.</span><span class="n">push</span><span class="p">(</span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">to_i32</span><span class="p">(</span><span class="n">gamma_bits</span><span class="p">)</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="n">to_i32</span><span class="p">(</span><span class="n">epsilon_bits</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">to_i32</span><span class="p">(</span><span class="n">bits</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">i32</span><span class="o">></span><span class="p">)</span><span class="w"> </span>-> <span class="kt">i32</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">r</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="p">(</span><span class="n">idx</span><span class="p">,</span><span class="w"> </span><span class="n">b</span><span class="p">)</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">bits</span><span class="p">.</span><span class="n">iter</span><span class="p">().</span><span class="n">enumerate</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">r</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="k">i32</span><span class="p">.</span><span class="n">pow</span><span class="p">((</span><span class="n">bits</span><span class="p">.</span><span class="n">len</span><span class="p">()</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="n">idx</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="kt">u32</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="n">r</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Các điểm chú ý:</p>
<ul>
<li>Rust dùng các kiểu khác nhau cho các loại số khác nhau. <code>bits.len()</code> hay <code>enumerate()</code>
là kiểu <code>usize</code>, chứa số nguyên không âm, dùng để đo kích thước. Nghe thì giống
u32 hay u64 nhưng Rust hay C++ coi đây là kiểu riêng biệt và lập trình viên phải
tự convert sang kiểu khác nếu muốn dùng khác đi. Số mũ của pow phải là kiểu u32.
Dùng <code>as u32</code> để ép kiểu từ <code>usize</code>. Cũng không thể viết <code>2.pow()</code> mà phải dùng
<code>2i32</code> để chỉ rõ kiểu của nó.</li>
<li>Dùng <code>vec.push(i)</code> để thêm <code>i</code> vào cuối <code>vec</code>, như Python list.append.</li>
<li><code>for (idx, b) in bits.iter().enumerate()</code> như Python <code>for idx, b in enumerate(bits)</code>,
tuple trong Python không cần thiết có <code>()</code> còn trong Rust là bắt buộc.</li>
<li>Có thể bỏ trống kiểu phần tử của Vec bằng dấu gạch dưới <code>_</code>: <code>Vec<_></code> do đoạn
code sau có phần push(), Rust sẽ tự suy ra kiểu dựa vào kiểu của giá trị được push.</li>
<li>Rust tự return dòng cuối cùng không có dấu <code>;</code>. Cũng có thể viết <code>return to_i32(gamma_bits) * to_i32(epsilon_bits);</code></li>
</ul>
<h3>Các bài hay, nổi bật</h3>
<ul>
<li>Day 15: tìm đường đi ít nguy hiểm nhất, có thể sử dụng thuật toán trong
sách giáo khoa "cấu trúc dữ liệu và giải thuật": Dijkstra (/ˈdaɪkstrəz/).
Google code sẵn hoặc lên <a href="https://en.wikipedia.org/wiki/Dijkstra's_algorithm#Pseudocode">wikipedia xem pseudocode</a> rồi viết lại.
Điểm thú vị là để tăng tốc thuật toán này, cần dùng khái niệm có tên
<a href="https://en.wikipedia.org/wiki/Dijkstra's_algorithm#Using_a_priority_queue">Priority Queue</a>, một loại queue đặc biệt, giúp tìm kiếm min hay max tức thì <code>O(1)</code>.
Trong Python có thể dùng <a href="https://docs.python.org/3/library/heapq.html"><code>import heapq</code></a>, trong Rust có sẵn kiểu <a href="https://doc.rust-lang.org/std/collections/struct.BinaryHeap.html">BinaryHeap</a>.</li>
<li>Không ít bài toán người chơi sẽ phải viết các "hàm đệ quy" - (Recursive function)
để giải, như <strong>metaverse</strong> của <a href="https://adventofcode.com/2021/day/21">day 21</a>.</li>
<li>Năm nay vắng bóng <a href="https://github.com/norvig/pytudes/blob/main/ipynb/Life.ipynb">"game of life"</a>, nhưng vẫn có một phiên bản trá hình trong <a href="https://adventofcode.com/2021/day/20">day 20</a>.</li>
<li>Day 16, người chơi phải viết 1 parser và chạy đoạn code <strong>BITS</strong> để tính toán,
hệ thống này giống một ngôn ngữ <a href="https://pp.pymi.vn/article/scm1/">nhà LISP</a> và có thể "cheat" ra <a href="https://github.com/hvnsweeting/adventofcode/blob/master/2021/src/day16.rs">dùng
LISP để giải cho nhanh</a></li>
<li>Day 18 có kiểu dữ liệu Binary Tree, nếu chưa đọc hết <a href="https://doc.rust-lang.org/stable/book/ch15-01-box.html#enabling-recursive-types-with-boxes">chương 15 "The Rust Book"</a> thì sẽ
khá khó để biểu diễn kiểu dữ liệu recursive này trên Rust. Nên tạm "cheat" ra
<a href="https://github.com/hvnsweeting/adventofcode/blob/master/2021/src/day18.ipynb">dùng Python để giải</a>.</li>
<li>Day 19 là bài phải code code trâu bò nhất. Tương tự day 20 của năm 2020, ám
ảnh người chơi mãi về sau với lượng code vài trăm dòng cần viết. Lý do: bài này
được ra vào ngày thứ 7/chủ nhật cuối cùng trong giải.</li>
<li>Và cũng có những bài giải tay nhiều khi nhanh hơn viết code như <a href="https://adventofcode.com/2021/day/23">day 23</a>. Giải
tay giống như đang chơi game.
Thậm chí có người chơi trên reddit đã <a href="https://www.reddit.com/r/adventofcode/comments/rmspb7/2021_day_23_it_was_nice_on_paper_but_its_even/">viết game
để giải bài này</a>.</li>
</ul>
<h2>Ownership, move, borrow & clone</h2>
<p>Khó có thể code Rust 25 ngày mà không động đến khái niệm borrow-checker, ownership.</p>
<p>Rust không có Garbage Collector (viết tắc GC).</p>
<p>Các ngôn ngữ như JavaScript, Python, Ruby, PHP, Go, Java, C# có GC nên lập
trình viên có thể tạo các giá trị tùy ý, GC sẽ theo dõi và tự xóa đi các giá trị
không dùng nữa.</p>
<p>Code C/C++ không có GC, lập trình viên cần tự viết code cấp phát (allocate) và giải phóng
(free) bộ nhớ (memory) để tạo các kiểu dữ liệu (trên heap).</p>
<p>Rust theo cách riêng của mình, đưa ra khái niệm "ownership" để biết ai là chủ
của 1 giá trị, giúp quản lý bộ nhớ mà không cần tới GC.</p>
<h4>Real "hello, Rust!"</h4>
<p>Chương trình sau, dựa vào các kiến thức từ ví dụ đã đưa ra, kết hợp kinh nghiệm
lập trình các ngôn ngữ khác thì thấy hoàn toàn hợp lý, Rust
compile báo lỗi:</p>
<div class="highlight"><pre><span></span><code><span class="k">fn</span> <span class="nf">sum</span><span class="p">(</span><span class="n">v</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">i32</span><span class="o">></span><span class="p">)</span><span class="w"> </span>-> <span class="kt">i32</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="k">mut</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">for</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">v</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">x</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">s</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">product</span><span class="p">(</span><span class="n">v</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">i32</span><span class="o">></span><span class="p">)</span><span class="w"> </span>-> <span class="kt">i32</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">v</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">_</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">_s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sum</span><span class="p">(</span><span class="n">v</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">_p</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">product</span><span class="p">(</span><span class="n">v</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Compile lỗi:</p>
<div class="highlight"><pre><span></span><code><span class="k">error</span><span class="err">[</span><span class="n">E0382</span><span class="err">]</span><span class="o">:</span> <span class="k">use</span> <span class="k">of</span> <span class="n">moved</span> <span class="k">value</span><span class="o">:</span> <span class="n n-Quoted">`v`</span>
<span class="o">--></span> <span class="n">main</span><span class="p">.</span><span class="n">rs</span><span class="o">:</span><span class="mi">14</span><span class="o">:</span><span class="mi">22</span>
<span class="o">|</span>
<span class="mi">12</span> <span class="o">|</span> <span class="n">let</span> <span class="n">v</span><span class="o">:</span> <span class="n">Vec</span><span class="o"><</span><span class="n">_</span><span class="o">></span> <span class="o">=</span> <span class="n">vec</span><span class="o">!</span><span class="err">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="err">]</span><span class="p">;</span>
<span class="o">|</span> <span class="o">-</span> <span class="n">move</span> <span class="n">occurs</span> <span class="n">because</span> <span class="n n-Quoted">`v`</span> <span class="n">has</span> <span class="k">type</span> <span class="n n-Quoted">`Vec<i32>`</span><span class="p">,</span> <span class="n">which</span> <span class="n">does</span> <span class="k">not</span> <span class="n">implement</span> <span class="n">the</span> <span class="n n-Quoted">`Copy`</span> <span class="n">trait</span>
<span class="mi">13</span> <span class="o">|</span> <span class="n">let</span> <span class="n">_s</span> <span class="o">=</span> <span class="nf">sum</span><span class="p">(</span><span class="n">v</span><span class="p">);</span>
<span class="o">|</span> <span class="o">-</span> <span class="k">value</span> <span class="n">moved</span> <span class="n">here</span>
<span class="mi">14</span> <span class="o">|</span> <span class="n">let</span> <span class="n">_p</span> <span class="o">=</span> <span class="n">product</span><span class="p">(</span><span class="n">v</span><span class="p">);</span>
<span class="o">|</span> <span class="o">^</span> <span class="k">value</span> <span class="n">used</span> <span class="n">here</span> <span class="k">after</span> <span class="n">move</span>
</code></pre></div>
<p>Rust compiler hướng dẫn chi tiết về vấn đề xảy ra, thậm chí đôi khi
đưa ra cả hướng dẫn sửa code. Lỗi xảy ra <code>use of moved value: v</code> với chỉ dẫn:</p>
<ul>
<li>tại dòng 13, sum nhận đầu vào v: "value moved here".</li>
<li>dòng 14, product nhận đầu vào v: "value used here after move".</li>
</ul>
<p>trong các ngôn ngữ lập trình khác, việc các đoạn code dùng chung 1 giá trị là chuyện
hoàn toàn bình thường và không có gì phải suy nghĩ, Rust thì khác.</p>
<h4>owner và move</h4>
<p>Khi gán biến,</p>
<div class="highlight"><pre><span></span><code>let x = vec![1,2,3];
let y = x;
let z = x; // not work, compile error: value used here after move
</code></pre></div>
<p>Ban đầu, <code>x = vec![1,2,3]</code>, x là owner (chủ sở hữu) của giá trị <code>vec![1,2,3]</code>.
Khi viết <code>y = x</code>, quyền sở hữu được chuyển
sang cho y. Sau dòng này, x không còn dính dáng tới <code>vec![1,2,3]</code>, hay không còn giá trị,
không thể dùng được nữa, nên không thể gán cho z được.</p>
<p>Tương tự, khi gọi function sum, sum sẽ trở thành chủ sở hữu mới của giá trị mà v đang chứa,
sau dòng này, v không còn hợp lệ. Việc chuyển đổi quyền sở hữu này gọi là
"move" ownership.</p>
<p>Thay vì chuyển quyền sở hữu, có thể thực hiện "mượn": borrow. Function sẽ khai báo
mình muốn own (sở hữu), hay muốn borrow (mượn).</p>
<p>Function sum viết lại để borrow, thêm dấu <code>&</code> trước kiểu của xs:
<code>fn sum(xs: &Vec<i32>) -> i32 {</code></p>
<p>và khi gọi function: <code>sum(&vec)</code>.</p>
<p>Ký hiệu <code>&</code> gọi là reference. <code>&v</code> tạo một reference <strong>refer</strong> (chỉ) đến giá trị của
vec nhưng không own nó.</p>
<div class="highlight"><pre><span></span><code><span class="k">fn</span> <span class="nf">sum</span><span class="p">(</span><span class="n">v</span>: <span class="kp">&</span><span class="nb">Vec</span><span class="o"><</span><span class="kt">i32</span><span class="o">></span><span class="p">)</span><span class="w"> </span>-> <span class="kt">i32</span> <span class="p">{</span><span class="w"></span>
<span class="c1">//...không đổi...</span>
<span class="p">}</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">product</span><span class="p">(</span><span class="n">v</span>: <span class="nb">Vec</span><span class="o"><</span><span class="kt">i32</span><span class="o">></span><span class="p">)</span><span class="w"> </span>-> <span class="kt">i32</span> <span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">1</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="k">fn</span> <span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">v</span>: <span class="nb">Vec</span><span class="o"><</span><span class="n">_</span><span class="o">></span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="fm">vec!</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">3</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">_s</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">sum</span><span class="p">(</span><span class="o">&</span><span class="n">v</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="n">_p</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">product</span><span class="p">(</span><span class="n">v</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Code mới sẽ chạy được, do sum chỉ borrow giá trị của v, chứ không own,
v vẫn là chủ của vector để sau đó, move cho product trở thành owner.</p>
<h4>Stack và heap <id="heap"></h4>
<p>Trong máy tính, có 2 loại bộ nhớ cấu trúc theo cách khác nhau:</p>
<p>Stack <strong>thường</strong> có kích thước nhỏ (VD: 2KB, 4KB, 8KB,... cũng có thể <a href="https://dave.cheney.net/2013/06/02/why-is-a-goroutines-stack-infinite">lớn dần lên đến vô cùng</a>), hoạt động như kiểu dữ liệu "stack" (last in first out - LIFO),
dữ liệu nhét vào stack (push) phải có kích thước biết trước (khi compile),
lấy dữ liệu từ stack (pop) thường nhanh hơn heap.
Các kiểu dữ liệu có thể chứa trong stack có kích thước biết trước: như
số (i32 - 32 bits, i64, f64, ...), string cố định (&str), array (<code>[3;i32]</code>).</p>
<p>Heap là vùng bộ nhớ tự do, khi muốn dùng phải yêu cầu hệ điều hành cấp cho (allocate),
dùng xong nếu không giải phóng trả lại hệ điều hành thì chương trình sẽ dùng
ngày càng nhiều RAM, gọi là memleak (memory leak).</p>
<p>Khi tạo 1 Vector hay HashMap trong Rust, kích thước của chúng có thể thay đổi
khi chạy (VD: thêm phần tử vào vector), nên chúng nằm trên heap. Lý do không
thấy code để alloc/free ở các ví dụ trên bởi Rust thực hiên tự động free giá trị khi biến
"out of scope" (thường là ra khỏi block <code>{}</code>).</p>
<p>Với các kiểu dữ liệu trên stack, không cần borrow bởi chúng sẽ tự copy do
các giá trị này nhỏ. Với các kiểu dữ liệu trên heap, cần gọi <code>.clone()</code> để
copy giá trị. Người mới code Rust có thể dùng <code>clone()</code> để tránh các vấn đề
ban đầu về ownership cho tới khi nắm được ownership & borrow.</p>
<p>Xem thêm tại <a href="https://doc.rust-lang.org/stable/book/ch04-01-what-is-ownership.html">đây</a>.</p>
<h2>Kết quả</h2>
<p>PyMi AoC 2021 kết thúc vào 12 giờ trưa ngày 26/12/2021, với giải thưởng:</p>
<p><img alt="leaderboard" src="http://pp.pymi.vn/images/aoc21_leaderboard.png"></p>
<ul>
<li>giải nhất: <a href="https://github.com/tung491/advent_to_code_2021">tung491</a> học viên PyMiHN1706</li>
<li>giải nhì: <a href="https://github.com/thevivotran">thevivotran</a> học viên PyMiHCM2008.</li>
<li>giải ba: stuncb97 học viên PyMiHN2010 - cựu vô địch 2020.</li>
<li>(ban tổ chức 2nd và khách mời 3rd không trong cơ cấu tính giải).</li>
</ul>
<p>Một tràng pháo tay cho các game thủ dù bận công việc vẫn nhịn ăn trưa cày
marathon code suốt 25 ngày 🎉😍</p>
<h2>Kết luận</h2>
<p>Advent of Code là một chuyến phiêu lưu thú vị hàng năm, là cơ hội tuyệt vời
để "vui vẻ" với code, học được thêm không ít điều mới mẻ.</p>
<p>Rust dù hơi dài dòng
so với Python, không hợp để code nhanh trong các cuộc thi, nhưng không phải
quá khó, lại là một vũ khí hạng nặng ngang C/C++ cho vào balo mang đi chiến khi cần.
Hàng ngàn lập trình viên đã liên tục vote Rust là ngôn ngữ yêu
thích nhất, còn bạn?</p>
<p>Và nhớ đừng quên đọc <a href="https://github.com/norvig/pytudes/blob/main/ipynb/Advent-2021.ipynb">văn mẫu</a> từ giám đốc nghiên cứu Google nhé!</p>
<p>Tạm biệt 2021, chúc mừng năm mới 2022!</p>
<h2>Finish</h2>
<p>Toàn bộ code giải 25 bài
(trừ bài 23 giải bằng giấy và bút) có trong <a href="https://github.com/hvnsweeting/adventofcode/tree/master/2021/src">repo</a>
PS: đây là code của 1 Rust newbie.</p>
<p><img src="http://pp.pymi.vn/images/aoc21.png" width=800></p>
<h2>Tham khảo</h2>
<ul>
<li>Stack vs Heap <a href="https://doc.rust-lang.org/stable/book/ch04-01-what-is-ownership.html">https://doc.rust-lang.org/stable/book/ch04-01-what-is-ownership.html</a></li>
<li><a href="https://matklad.github.io//2020/09/20/why-not-rust.html">https://matklad.github.io//2020/09/20/why-not-rust.html</a></li>
</ul>
<h2>Ủng hộ tác giả</h2>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
</ul>Ký sự chiếm cờ tại SNYK CTF 20212021-10-06T00:00:00+07:002021-10-06T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2021-10-06:/article/ctf/<p>SNYK CTF 2021 write-up</p><h2>CTF là gì?</h2>
<p>Capture the Flag (CTF) là một trò chơi được ưa chuộng của những người trong
ngành bảo mật thông tin, thường tổ chức theo dạng cuộc thi với nhiều đội
tham gia và có đội dành chiến thắng.</p>
<p>CTF có 3 dạng phổ biến:</p>
<ul>
<li>Jeopardy-style CTFs: Đề thi thường gồm nhiều dạng bài thuộc
các lĩnh vực khác nhau trong ngành: cryptography, stego, binary analysis,
reverse engineering, mobile security, web hacking, OS, Linux...</li>
<li>attack-defence: mỗi đội được giao cho 1 hệ thống có các lỗi bảo mật, và 2 đội
sẽ vá hệ thống của mình đồng thời tấn công hệ thống của đội khác.</li>
<li>mixed: đủ dạng</li>
</ul>
<p>CTF với người làm bảo mật giống như
<a href="https://www.leagueoflegends.com/en-us/">LOL</a> với các thanh niên chơi esport
vậy, cũng có các giải đấu lớn thế giới.
Danh sách các giải đấu lớn có thể xem trên <a href="https://ctftime.org/ctfs">ctftime</a>,
nổi tiếng nhất có thể kể tới DEF CON CTF, phổ biến nhất có thể gọi tên Google CTF.</p>
<h2>SNYK CTF 2021</h2>
<p><a href="https://snyk.io/">SNYK.io</a> là một công ty làm về bảo mật, cung cấp các dịch vụ
phát hiện lỗi bảo mật tích hợp vào hệ thống khi phát triển phần mềm ở các doanh
nghiệp và cộng đồng opensource. Năm nay snyk tổ chức CTF và team pymi nhận được
<a href="https://mailchi.mp/pythonweekly/python-weekly-issue-519">lời quảng cáo trên "PythonWeekly" email</a>, chiến thôi.
Đây là lần thứ 2 HVN tham gia một giải CTF, lần đầu là tại <a href="https://viblo.asia/p/code-war-2017-online-round-write-ups-part-1-aWj531Y1Z6m">Framgia
Code War
2017</a>,
bẵng cái 4 năm, không có kinh nghiệm gì mới do công
việc chẳng liên quan tới hắc hiếc gì.</p>
<p><strong>https://ctf.snyk.io/ Fetch the Flag at SnykCon 2021!</strong></p>
<p><strong>October 5, 9:00 am - 7:00 pm ET</strong></p>
<p>đăng ký rồi rủ rê team 5 người.</p>
<p>Cách tính điểm: 500 cho mỗi bài, giảm dần theo số lượt giải. Tức giải xong sớm
thì sẽ được điểm cao, sau khi giảm dần, điểm có thể tới min là 50.</p>
<p>Các bài thi sẽ cần đi tìm 1 đoạn flag dạng <code>SNYK{...}</code> rồi điền vào website
của snyk.</p>
<h2>Cảnh báo</h2>
<p>Code trong các cuộc thi CTF thường được viết ra nhanh nhất, nên thường không
theo các chuẩn sạch gọn đẹp hay tối ưu, nó đơn giản là thứ bạn viết ra khi có
sức ép về mặt thời gian và mục tiêu là kết quả.
Chỉ nên dùng để tham khảo, tránh dùng làm văn mẫu.</p>
<h3>Cảnh báo 2</h3>
<p>Bạn đọc chưa quen phần nào có thể bỏ qua phần đó, các bài chia theo các lĩnh
vực khác nhau.</p>
<p>Bài viết bởi HVN, các phần do các tác giả khác viết có ghi rõ trong từng bài.</p>
<h2>Các bài đã giải trong thời gian thi đấu</h2>
<p><img alt="done" src="http://pp.pymi.vn/images/ctf_solved.jpg"></p>
<h2>Coding</h2>
<h3>CALC-UL8R</h3>
<p>Đề cho 1 địa chỉ để nc vào</p>
<div class="highlight"><pre><span></span><code>$ nc <span class="m">35</span>.211.207.36 <span class="m">8000</span>
____ _ _ ____ _ _ _ ___ ____
/ ___<span class="p">|</span> / <span class="se">\ </span> <span class="p">|</span> <span class="p">|</span> / ___<span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="o">(</span> _ <span class="o">)</span><span class="p">|</span> _ <span class="se">\</span>
<span class="p">|</span> <span class="p">|</span> / _ <span class="se">\ </span><span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span> _____<span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span> <span class="p">|</span> / _ <span class="se">\|</span> <span class="p">|</span>_<span class="o">)</span> <span class="p">|</span>
<span class="p">|</span> <span class="p">|</span>___ / ___ <span class="se">\|</span> <span class="o">||</span> <span class="o">||</span>_____<span class="p">|</span> <span class="p">|</span>_<span class="p">|</span> <span class="p">|</span> <span class="p">|</span>__<span class="p">|</span> <span class="o">(</span>_<span class="o">)</span> <span class="p">|</span> _ <
<span class="se">\_</span>___/_/ <span class="se">\_\_</span>____<span class="se">\_</span>___<span class="p">|</span> <span class="se">\_</span>__/<span class="p">|</span>_____<span class="se">\_</span>__/<span class="p">|</span>_<span class="p">|</span> <span class="se">\_\</span>
<span class="m">31521</span> * <span class="m">2455</span> - <span class="m">29590</span> - o - <span class="m">40881</span> + <span class="nv">34681</span> <span class="o">=</span> <span class="m">77331423</span>
<span class="nv">o</span> <span class="o">=</span>
</code></pre></div>
<p>cần tính giá trị của biến, trong ví dụ này là <code>o</code>, rồi nhập vào.
Cứ nhập xong, enter, phía server sẽ trả về 1 phép tính khác.</p>
<p>Vậy có 2 việc cần làm:</p>
<ul>
<li>kết nối đến server để nhận đề và gửi kết quả: việc này có thể dùng Python telnetlib</li>
<li>đọc biểu thức và tính ra kết quả</li>
</ul>
<p>Do lần đầu dùng <a href="https://docs.python.org/3/library/telnetlib.html#telnet-example"><code>telnetlib</code></a>, nên cũng khá vất vả một lúc mới tìm
ra cách đọc dùng <a href="https://pp.pymi.vn/article/10x/">regex</a>
thay vì dùng string.</p>
<ul>
<li><code>read_until("string")</code> sẽ đọc đến khi thấy "string" thì trả nội dung về</li>
<li><code>expect(list, timeout=None)</code> đọc đến khi 1 trong các regex pattern match.</li>
</ul>
<p>Sau khi đã gửi nhận được, cần viết code giải phương trình, ban đầu mình có tự viết code để giải phương trình bằng cách thay biến trong phương trình (1 ký tự, dùng regex) bằng số 0, rồi chuyển vế các phép tính còn lại. Cách làm đơn giản này đúng cho đến khi nó sai: biểu thức có phép nhân. Nghĩ tới giải phương trình trên Python là nghĩ tới sympy, search <code>sympy solve equation</code> thấy ngay</p>
<ul>
<li><a href="https://stackoverflow.com/a/30776918/807703">https://stackoverflow.com/a/30776918/807703</a></li>
</ul>
<p>sửa lại để nhận mọi biến, PS: ở đây mình ko chăm chỉ viết tay từ a đến z mà gõ 1 dòng Python là xong</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">string</span><span class="p">;</span> <span class="s1">', '</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">string</span><span class="o">.</span><span class="n">ascii_lowercase</span><span class="p">)</span>
<span class="s1">'a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z'</span>
</code></pre></div>
<p>Code giải phương trình</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">sympy</span> <span class="kn">import</span> <span class="n">solve</span>
<span class="kn">from</span> <span class="nn">sympy.abc</span> <span class="kn">import</span> <span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">,</span> <span class="n">d</span><span class="p">,</span> <span class="n">e</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="n">g</span><span class="p">,</span> <span class="n">h</span><span class="p">,</span> <span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">,</span> <span class="n">k</span><span class="p">,</span> <span class="n">l</span><span class="p">,</span> <span class="n">m</span><span class="p">,</span> <span class="n">n</span><span class="p">,</span> <span class="n">o</span><span class="p">,</span> <span class="n">p</span><span class="p">,</span> <span class="n">q</span><span class="p">,</span> <span class="n">r</span><span class="p">,</span> <span class="n">s</span><span class="p">,</span> <span class="n">t</span><span class="p">,</span> <span class="n">u</span><span class="p">,</span> <span class="n">v</span><span class="p">,</span> <span class="n">w</span><span class="p">,</span> <span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">,</span> <span class="n">z</span>
<span class="kn">from</span> <span class="nn">sympy.parsing.sympy_parser</span> <span class="kn">import</span> <span class="n">parse_expr</span>
<span class="k">def</span> <span class="nf">solve_meThis</span><span class="p">(</span><span class="n">string_</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">lhs</span> <span class="o">=</span> <span class="n">parse_expr</span><span class="p">(</span><span class="n">string_</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"="</span><span class="p">)[</span><span class="mi">0</span><span class="p">])</span>
<span class="n">rhs</span> <span class="o">=</span> <span class="n">parse_expr</span><span class="p">(</span><span class="n">string_</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">"="</span><span class="p">)[</span><span class="mi">1</span><span class="p">])</span>
<span class="n">solution</span> <span class="o">=</span> <span class="n">solve</span><span class="p">(</span><span class="n">lhs</span><span class="o">-</span><span class="n">rhs</span><span class="p">)</span>
<span class="k">return</span> <span class="n">solution</span>
<span class="k">except</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"invalid equation"</span><span class="p">)</span>
<span class="n">equation</span> <span class="o">=</span> <span class="n">text</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">)</span><span class="o">.</span><span class="n">splitlines</span><span class="p">()[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span>
<span class="n">valid_text</span> <span class="o">=</span> <span class="n">equation</span>
<span class="p">[</span><span class="n">result</span><span class="p">]</span> <span class="o">=</span> <span class="n">solve_meThis</span><span class="p">(</span><span class="n">valid_text</span><span class="p">)</span>
</code></pre></div>
<p>Thực hiện gửi nhận liên tục các phép tính cho đến khi server trả về kết quả:</p>
<div class="highlight"><pre><span></span><code><span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="sa">b</span><span class="s1">'</span><span class="se">\n</span><span class="s1">SNYK</span><span class="si">{37d779963c037715c02624b6963008f55e92d12e8714a15b7a905c1c997d1afc}</span><span class="se">\n</span><span class="s1">'</span><span class="p">)</span>
</code></pre></div>
<p>File Jupyter Notebook dùng giải bài này
<a href="https://gist.github.com/hvnsweeting/7e00e139912b9d65a1ec7c1913fdb513">https://gist.github.com/hvnsweeting/7e00e139912b9d65a1ec7c1913fdb513</a></p>
<h3>Random flag generator - python</h3>
<p>Một bài được tag thẻ <code>python</code>, cho 1 file code python và 1 file log:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">random</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">hashlib</span>
<span class="n">seed</span> <span class="o">=</span> <span class="nb">round</span><span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">())</span>
<span class="n">random</span><span class="o">.</span><span class="n">seed</span><span class="p">(</span><span class="n">seed</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">rnd</span> <span class="o">=</span> <span class="n">random</span><span class="o">.</span><span class="n">random</span><span class="p">()</span>
<span class="nb">hash</span> <span class="o">=</span> <span class="n">hashlib</span><span class="o">.</span><span class="n">sha256</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">rnd</span><span class="p">)</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span><span class="o">.</span><span class="n">hexdigest</span><span class="p">()</span>
<span class="n">flag</span> <span class="o">=</span> <span class="sa">f</span><span class="s2">"SNYK</span><span class="se">{{</span><span class="si">{</span><span class="nb">hash</span><span class="si">}</span><span class="se">}}</span><span class="s2">"</span>
<span class="k">if</span> <span class="s2">"5bc"</span> <span class="ow">in</span> <span class="nb">hash</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">"./flag"</span><span class="p">,</span> <span class="s2">"w"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">flag</span><span class="p">)</span>
<span class="k">break</span>
<span class="k">else</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s2">"Bad random value: </span><span class="si">{</span><span class="n">rnd</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Flag created 🎉"</span><span class="p">)</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">3719072557403058</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">3702330745519661</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">0634360689087381</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">2952684217196877</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">49843979869018884</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">7895773927381043</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">2917373566923527</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">9030776618431813</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">7181809628413409</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">28050872595896736</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">17458286936713008</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">2767390568969583</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">5492478684168797</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">2641653670084557</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">5156703392963877</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">32839693347899057</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">6998299885658202</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">5811672985185747</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">4644468325648108</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">49982517906634727</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">9333988943747559</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">7513893164652713</span>
<span class="nv">Bad</span> <span class="k">random</span> <span class="nv">value</span>: <span class="mi">0</span>.<span class="mi">18638831058360805</span>
<span class="nv">Flag</span> <span class="nv">created</span> 🎉
</code></pre></div>
<p>Đọc code thấy để tìm được flag, cần tìm ra
giá trị <code>seed</code> mà người ra đề đã dùng.
Các học viên <a href="https://pymi.vn">học Python tại Pymi.vn</a> đều được học: các function trong <code>random</code> chỉ là "gỉa ngẫu nhiên" và thực chất là chạy thuật toán sinh số ngẫu nhiên dựa trên giá trị <code>seed</code>. <code>seed</code> trong bài này gợi ý là UNIX timestamp, chạy từ 0 tới khoảng 1 tỷ 6 (1633537375).
Cách tìm đơn giản là sửa lại code, chạy lần lượt với từng seed, so sánh đầu ra (thay vì print thì cho vào 1 string) với file log. Nếu giống nhau tức đó là gía trị seed cần tìm.</p>
<p>Vấn đề ở cách làm này, khi Python thực hiện khoảng 16 triệu phép +1 mỗi giây (xem <a href="https://cpu.pymi.vn/">cpu.pymi.vn</a>), thì để tính 1 tỷ 6 phải mất ít nhất 100 giây.
Mỗi giá trị seed lại sinh nhiều random value, thời gian sẽ gấp thêm 20 - 30 lần. Và khi mang chạy thật, mỗi giây nó tính khoảng được 5000-10000 seed. Tức quá chậm và cần tăng tốc.
Ném thêm các giải pháp như dùng thread/multiprocess cũng không khá hơn là bao.
Sau 30 phút, 1 tiếng không ra kêt quả, và giải xong 1 bài khác trong thời gian chờ này, mình quay lại tối ưu code.</p>
<p>Thay vì tính hết output của mỗi seed, cho nó dừng lại ngay nếu dòng log đầu tiên khác với dòng đầu tiên trong log.txt.
Sau 1-2 phút đã có kết quả.</p>
<p>Code: <a href="https://gist.github.com/hvnsweeting/619ecf04aa9b57bd6b44f3fcc57fe8c2">https://gist.github.com/hvnsweeting/619ecf04aa9b57bd6b44f3fcc57fe8c2</a></p>
<h3>Russian doll</h3>
<p>Đề bài cho ở dạng đã mã hóa:</p>
<blockquote>
<p>Esp qwlr td DOKnGoIgKSsVvizaEAJmEgxiEShQKjjgyfeLhdutuIhObpZr IIEPL pyncjaepo. Alddhzco stye: iiii.</p>
</blockquote>
<p>và sau khi @pham <a href="https://planetcalc.com/1434/">dùng tool</a> để giải mã <a href="https://www.familug.org/2014/12/programming-phe-rot13.html">ROT15</a> thì thu được nội dung:</p>
<blockquote>
<p>The flag is SDZcVdXvZHhKkxopTPYbTvmxTHwFZyyvnutAwsjijXwDqeOg XXTEA encrypted. Password hint: xxxx.</p>
</blockquote>
<p>CTF thường là vậy, sau lớp này sẽ qua lớp khác.
Giờ để ý lại tên bài, cũng với hàm ý tương tự, Russian doll Matryoshka, trong con này là con khác.</p>
<p><img alt="doll" src="https://images.unsplash.com/photo-1613981948475-6e2407d8b589?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&dl=didssph-PB80D_B4g7c-unsplash.jpg&w=640"></p>
<p><center>
Photo by <a href="https://unsplash.com/@didsss?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Didssph</a> on <a href="https://unsplash.com/s/photos/russian-doll?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a>
</center></p>
<p>với lượng điểm thu được cho bài này là 490/500 lúc October 6th, 2:44:17 AM, sau ~7 tiếng, thì đây rõ là 1 bài khó.
Hoặc nó tiết lộ 1 phần về công cụ của người chơi đều là Python, vì sao hãy đọc tiếp...
Cho một thuật toán mã hóa cho trước, với key là 4 ký tự, hẳn không khó khăn gì các team có thể tải ngay lib Python trên mạng về và bruteforce vài phút là có ngay kết quả. Đen thay, 2 thư viện tìm thấy đầu tiên, đều có vẻ không dùng được</p>
<ul>
<li><a href="https://pypi.org/project/xxtea/">https://pypi.org/project/xxtea/</a></li>
</ul>
<p>có 1 dòng yêu cầu <code># Key must be a 16-byte string.</code>
Trong khi bài này key là 4 ký tự.</p>
<ul>
<li><a href="https://pypi.org/project/xxtea-py/">https://pypi.org/project/xxtea-py/</a></li>
</ul>
<p>một cái lib khá oái oăm khi cài thì ok mà dùng thì lại đòi cffi, và hầu hết mọi người dừng lại ở đó.</p>
<p>Vậy phải làm sao? kết quả khi search cũng trả về nhiều thư viện cho ngôn ngữ khác như C, C++, Golang... mà ngồi viết C sau 10 năm không viết thì rất căng.
Nhưng cuối cùng, Golang lại là giải pháp, nhờ vài năm code Go ăn tiền, sau 5 phút, ten ten có luôn kết quả:</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="p">(</span>
<span class="s">"encoding/base64"</span>
<span class="s">"fmt"</span>
<span class="s">"log"</span>
<span class="s">"strings"</span>
<span class="s">"github.com/xxtea/xxtea-go/xxtea"</span>
<span class="p">)</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">encodedString</span> <span class="o">:=</span> <span class="s">"SDZcVdXvZHhKkxopTPYbTvmxTHwFZyyvnutAwsjijXwDqeOg"</span>
<span class="nx">originalStringBytes</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">base64</span><span class="p">.</span><span class="nx">StdEncoding</span><span class="p">.</span><span class="nx">DecodeString</span><span class="p">(</span><span class="nx">encodedString</span><span class="p">)</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"Some error occured during base64 decode. Error %s"</span><span class="p">,</span> <span class="nx">err</span><span class="p">.</span><span class="nx">Error</span><span class="p">())</span>
<span class="p">}</span>
<span class="nx">key</span> <span class="o">:=</span> <span class="s">"1234567890"</span>
<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">a</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">key</span> <span class="p">{</span>
<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">b</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">key</span> <span class="p">{</span>
<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">c</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">key</span> <span class="p">{</span>
<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">d</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">key</span> <span class="p">{</span>
<span class="nx">keyNow</span> <span class="o">:=</span> <span class="nb">string</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="o">+</span> <span class="nb">string</span><span class="p">(</span><span class="nx">b</span><span class="p">)</span> <span class="o">+</span> <span class="nb">string</span><span class="p">(</span><span class="nx">c</span><span class="p">)</span> <span class="o">+</span> <span class="nb">string</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span>
<span class="nx">decrypt_data</span> <span class="o">:=</span> <span class="nb">string</span><span class="p">(</span><span class="nx">xxtea</span><span class="p">.</span><span class="nx">Decrypt</span><span class="p">(</span><span class="nx">originalStringBytes</span><span class="p">,</span> <span class="p">[]</span><span class="nb">byte</span><span class="p">(</span><span class="nx">keyNow</span><span class="p">)))</span>
<span class="k">if</span> <span class="nx">strings</span><span class="p">.</span><span class="nx">Contains</span><span class="p">(</span><span class="nx">decrypt_data</span><span class="p">,</span> <span class="s">"SNYK"</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%s\n"</span><span class="p">,</span> <span class="nx">decrypt_data</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p><a href="https://gist.github.com/hvnsweeting/b8d518fdd67b85e9bf9f6a16af6221af">Code</a></p>
<p>kết luận ở đây là thành thạo thêm một ngôn ngữ backup phổ biến như
C/C++/Java/C#/Golang sẽ rất hữu ích khi không dùng được Python. Nói thì dễ, chứ
thành thạo 1 ngôn ngữ đến mức dùng được lúc áp lực thời gian không phải chuyện
ai cũng có thời gian/tiền của đầu tư, giải pháp khác có vẻ dễ hơn là kiếm team
member với tool set khác nhau.</p>
<p>PS: lib <code>xxtea-py</code> sau khi cài <code>sudo apt install -y build-essential python3-dev</code>
và <code>pip install cffi xxtea-py</code> trên Ubuntu 20.04, chạy được ra kết quả</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">base64</span>
<span class="kn">import</span> <span class="nn">xxtea</span>
<span class="n">s</span> <span class="o">=</span> <span class="s1">'SDZcVdXvZHhKkxopTPYbTvmxTHwFZyyvnutAwsjijXwDqeOg'</span>
<span class="n">secret</span> <span class="o">=</span> <span class="n">base64</span><span class="o">.</span><span class="n">decodestring</span><span class="p">(</span><span class="n">s</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">))</span>
<span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="k">for</span> <span class="n">b</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="k">for</span> <span class="n">c</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">xxtea</span><span class="o">.</span><span class="n">decrypt</span><span class="p">(</span><span class="n">secret</span><span class="p">,</span> <span class="s2">"</span><span class="si">{}{}{}{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">a</span><span class="p">,</span><span class="n">b</span><span class="p">,</span><span class="n">c</span><span class="p">,</span><span class="n">d</span><span class="p">))</span>
<span class="k">if</span> <span class="sa">b</span><span class="s2">"SNYK"</span> <span class="ow">in</span> <span class="n">r</span><span class="p">:</span> <span class="n">exit</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
</code></pre></div>
<h2>Linux/system</h2>
<h3>All your flags are belong to root - Linux CLI</h3>
<p>Bài cho 1 user <code>u</code>, password và 1 địa chỉ để SSH vào.
Sau khi login, thấy như sau:</p>
<div class="highlight"><pre><span></span><code>all-your-flags-are-belong-to-root-p4j0:~$
</code></pre></div>
<p>gõ <code>ls</code> không thấy file gì. <code>cd</code> lung tung, tới <code>/</code>, <code>ls</code> thấy file <code>/flag</code> nhưng file này chỉ <code>root</code> mới đọc được.</p>
<p>Gõ thử <code>sudo</code> không có, gõ <code>su -l</code> để
trở thành root nhận được 1 message:</p>
<div class="highlight"><pre><span></span><code>all-your-flags-are-belong-to-root-p4j0:/$ su -l
su: must be suid to work properly
$ ls -la <span class="sb">`</span>which su<span class="sb">`</span>
lrwxrwxrwx <span class="m">1</span> root root <span class="m">12</span> Jun <span class="m">15</span> <span class="m">14</span>:34 /bin/su -> /bin/busybox
</code></pre></div>
<p>File su này khá khác thường so với máy bình thường:</p>
<div class="highlight"><pre><span></span><code>~$ ls -la `which su`
-rwsr-xr-x 1 root root 67816 Jul 21 2020 /usr/bin/su
</code></pre></div>
<p>Để từ user thường chiếm được quyền root đọc file /flag, cần "làm cách nào đó", và lời gợi ý là <strong>suid</strong>.</p>
<p><code>SUID</code> là khái niệm ít phổ biến với người dùng CLI thông thường, họ học hết chmod 755 777 400 là khá đủ rồi.
<code>SUID</code> là một giá trị đặc biệt để cấp quyền cho user, khi user chạy chương trình sẽ dùng UID của người sở hữu file thay vì UID của user, hay
nói cách khác, trở thành người sở hữu / "chiếm quyền" trong lúc chạy chương trình này.
Khi chmod, set SUID sử dụng số <code>4</code> trước số chmod thông thường. Ví dụ <code>4755</code>.
Lệnh <code>su</code> ở trên là 1 ví dụ có SUID.
Lý do mình biết tới SUID, do công việc trước đây có viết một chương trình thực hiện gửi ICMP (ping), mà lại yêu cầu quyền root. Trong khi bình thường gõ lệnh ping thì không phải sudo/su bao giờ. Hóa ra <a href="https://security.stackexchange.com/a/222800/11544">lệnh ping (ngày xưa) set SUID</a> (giờ ko set nữa).</p>
<p>Dùng <code>find</code> tìm trên máy các file có set SUID:</p>
<div class="highlight"><pre><span></span><code>$ find / -perm -4000
</code></pre></div>
<p>Tìm thấy file lệnh <code>curl</code>. <code>curl</code> là chương trình thường dùng để gửi HTTP request, nó cũng đọc được file khi thay <code>http://</code> bằng <code>file://</code></p>
<div class="highlight"><pre><span></span><code>all-your-flags-are-belong-to-root-p4j0:/$ curl file:///flag
SNYK{06b0e0ae4995af71335eda2882fecbc5008b01d95990982b439f3f8365fc07f7}
</code></pre></div>
<p>PS: Nhìn lại, nếu hiểu ý của đề thì đó là lời gợi ý file flag nằm ở <code>/</code> (root).</p>
<p>Ref</p>
<ul>
<li><a href="https://security.stackexchange.com/a/222800/11544">https://security.stackexchange.com/a/222800/11544</a></li>
<li><a href="https://www.redhat.com/sysadmin/suid-sgid-sticky-bit">https://www.redhat.com/sysadmin/suid-sgid-sticky-bit</a></li>
<li><a href="https://www.linuxjournal.com/content/gettin-sticky-it">https://www.linuxjournal.com/content/gettin-sticky-it</a></li>
<li><a href="https://www.linuxnix.com/suid-set-suid-linuxunix/">https://www.linuxnix.com/suid-set-suid-linuxunix/</a></li>
</ul>
<h3>Robert Louis Stevenson - docker</h3>
<p>Đề cho 1 file Docker image chứa "kho báu". Tải file này về,
không nhớ chính xác là tên gì, tạm gọi là <code>file.tar</code>.
Bản chất các file "chương trình"/"data" trên máy tính thường là một dạng file archive/nén như zip/tar.
Docker image cũng vậy:</p>
<div class="highlight"><pre><span></span><code># tar xf ../file.tar
# grep -Rin SNYK .
Binary file ./b3b0b5528b213a9d35315784c9907fdeb5d8bf89a0bb012ee63546b3a1c2e10b/layer.tar matches
# tar xf .././b3b0b5528b213a9d35315784c9907fdeb5d8bf89a0bb012ee63546b3a1c2e10b/layer.tar
# grep -Rin SNYK
ak/pp/tv/bc/22/flag:1:SNYK{23acc4111e1905ba1832cab7f1660284e3d1b91d3c2ead7bcec41ee8a4bd5ce9}
</code></pre></div>
<p>Ref:</p>
<ul>
<li><a href="https://www.familug.org/2012/09/nen-giai-nen-bang-command-line-trong.html">https://www.familug.org/2012/09/nen-giai-nen-bang-command-line-trong.html</a></li>
<li><a href="https://github.com/moby/moby/blob/master/image/spec/v1.2.md#combined-image-json--filesystem-changeset-format">https://github.com/moby/moby/blob/master/image/spec/v1.2.md#combined-image-json--filesystem-changeset-format</a></li>
<li><a href="https://github.com/hvnsweeting/pocker">https://github.com/hvnsweeting/pocker</a></li>
<li><a href="grep: https://www.familug.org/2012/10/vai-combo-lenh-de-nho-d-se-uoc-update.html">grep: https://www.familug.org/2012/10/vai-combo-lenh-de-nho-d-se-uoc-update.html</a></li>
</ul>
<p>PS: Robert Louis Stevenson là tác giả của truyện "đảo giấu vàng" (Treasure Island)</p>
<h2>Steganography (stego - giấu tin trong ảnh)</h2>
<h3>qrrr</h3>
<p>via <a href="https://github.com/khanhduy8">khanhduy8</a></p>
<p><img alt="qr" src="http://pp.pymi.vn/images/ctf_qrrr.png"></p>
<p>Bài cho một file ảnh QR đủ màu sắc.
Lấy zalo ra quét thử không được, như vậy file này thực ra không phải QR đúng chuẩn.
Nhìn vào màu sắc của hình thì có vẻ như QR này gồm 3 mã QR tương ứng với 3 đoạn mà khi ghép lại với nhau ta có được flag.
OK. Giờ dùng một công cụ đơn giản để xử lý file ảnh này. Link Tool: <a href="https://stegonline.georgeom.net/upload">stegonline.georgeom.net</a>
Một file ảnh màu RGB này có 3 bit planes là (Red, Green, Blue).
Thử với plane Red với giá trị là 6/8 <a href="https://i.ibb.co/zX5y40c/red.png">ta có</a>,
trông có vẻ ổn nhưng với ảnh QR để quét thì ta cần reverse lại màu. Sau khi reverse ta được</p>
<p><img alt="reversed" src="http://pp.pymi.vn/images/ctf_qrrr2.png"></p>
<p>Quét mã này ra: <code>12d99aa3a92f1abbb7d40786</code>
Do không có {} nên đây chắc là đoạn giữa
Tương tự thử với Green 6 được: SNYK{6947bd4818ffc1768f2
Với Green 7: 5ff8d4e4958d8007a3897}
Ghép 3 đoạn lại ra flag: <code>SNYK{6947bd4818ffc1768f212d99aa3a92f1abbb7d407865ff8d4e4958d8007a3897}</code></p>
<p>PS: ngày hôm sau, khanhduy8 nhận ra qrrr là lời gợi ý về 3 mã qr.</p>
<p>PPS: hvn dùng Firefox trên Android mở ra ngay phần đuôi trong 3 mã, kết luận: không dùng zalo.</p>
<h2>Exploit (khai thác lỗ hổng bảo mật)</h2>
<h3>Invisible Ink - javascript</h3>
<p>via <a href="https://github.com/khanhduy8">khanhduy8</a></p>
<p>Bài này cho 1 link web và một <a href="http://pp.pymi.vn/ctf/index.js">file source code</a>, 1 file <a href="http://pp.pymi.vn/ctf/package.json">package.json</a>.
Có thể đọc source, thấy nghi nghi rồi google thư viện <code>lodash</code>, nhưng pro @hvn
setup <a href="https://docs.snyk.io/">công cụ của Snyk</a>
để quét rồi nên ta có kết quả vulnerbility</p>
<p><img alt="snyk scan" src="http://pp.pymi.vn/images/ctf_snyk_scan.jpeg"></p>
<p>Chú ý đến vul thứ 2. Đây là PoC của exploit vul này <a href="https://snyk.io/vuln/SNYK-JS-LODASH-450202">Prototype Pollution in lodash | Snyk</a>
Trong file source code có đoạn check:
<code>if(output.flag)</code>
nếu <code>true</code> sẽ response giá trị của flag
biến output hiện tại đang là:
<code>output = {}</code> nên sẽ không trả về kết quả chúng ta cần
Trong source code có sử dụng Unsafe Object recursive merge</p>
<div class="highlight"><pre><span></span><code><span class="k">merge</span><span class="w"> </span><span class="p">(</span><span class="n">target</span><span class="p">,</span><span class="w"> </span><span class="n">source</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="n">foreach</span><span class="w"> </span><span class="n">property</span><span class="w"> </span><span class="k">of</span><span class="w"> </span><span class="n">source</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="n">property</span><span class="w"> </span><span class="ow">exists</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="k">is</span><span class="w"> </span><span class="n">an</span><span class="w"> </span><span class="k">object</span><span class="w"> </span><span class="k">on</span><span class="w"> </span><span class="k">both</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">target</span><span class="w"> </span><span class="ow">and</span><span class="w"> </span><span class="n">the</span><span class="w"> </span><span class="n">source</span><span class="w"></span>
<span class="w"> </span><span class="k">merge</span><span class="p">(</span><span class="n">target</span><span class="o">[</span><span class="n">property</span><span class="o">]</span><span class="p">,</span><span class="w"> </span><span class="n">source</span><span class="o">[</span><span class="n">property</span><span class="o">]</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="n">target</span><span class="o">[</span><span class="n">property</span><span class="o">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">source</span><span class="o">[</span><span class="n">property</span><span class="o">]</span><span class="w"></span>
</code></pre></div>
<p>trong đó target là output còn source là request nên chỉ cần thay request bình thường từ:
<code>{"message": "ping"}</code>
sang
<code>{"constructor": {"prototype": {"flag": true}}}</code>
khi này thì Object đã bị thêm vào thuộc tính <code>flag:true</code>
Do đó <code>output.flag</code> sẽ trả về true. Ta có response chứa flag:
<code>SNYK{6a6a6fff87f3cfdca056a077804838d4e87f25f6a11e09627062c06f142b10dd}</code></p>
<p><img alt="snyk scan" src="http://pp.pymi.vn/images/ctf_lodash.jpeg"></p>
<h2>Kết quả</h2>
<p>Team PyMi xếp thứ 44 / 537 đội có ghi điểm, có lúc xếp thứ 24. 3h buồn ngủ quá
ae lăn quay hết nên tụt hạng mạnh :))</p>
<p><img alt="44" src="http://pp.pymi.vn/images/ctf_rank.png"></p>
<p>Theo đánh giá của 1 dân chơi thì giải CTF này thuộc loại trung bình, chưa khó,
nhưng không phải game chuyên nghiệp do chỉ kéo dài 10 tiếng và lợi thế về
múi giờ cho bên đông Mỹ (8PM giờ Việt Nam -> 6AM), các giải chuyên nghiệp sẽ
kéo dài 24h để đảm bảo công bằng.</p>
<p>Đi thi với tinh thần cọ sát, các bài thi rất thú vị, học được nhiều điều mới
nên rất vui.</p>
<h2>Kết luận</h2>
<p>CTF là một <strong>trò chơi</strong> thú vị. Như mọi trò chơi khác, nó dễ gây nghiện, và
nghiện quá là không tốt. CTF có loại khó, có loại không khó tẹo nào, để bắt đầu
chơi, hãy học dùng lệnh trên Linux, lập trình 1 ngôn ngữ bất kỳ
và tham gia thử các game dễ như trên <a href="https://overthewire.org/wargames/">overthewire.org</a>
hay khó hơn là <a href="https://capturetheflag.withgoogle.com/beginners-quest">Google CTF beginners quest</a>
chơi nhiều là khác quen, và làm quen với chuyện "không phải bài nào mình cũng giải được".</p>
<h2>Updated 2022-02-10 - phần 2 by @pham</h2>
<h4>Lời nói đầu</h4>
<p>Đây là một giải CTF của snyk.io - một tổ chức phát triển platform dạng "Audit source code", như nhận định ban đầu thì các task thiên về dạng programming, misc, và một số bài là lỗ hổng web mức độ medium.</p>
<p>Trong giải thì mình khá là phế khi không giải được mấy task quan trọng, mặc dù đã nhìn ra cách giải, nhưng payload không hiểu sao không work, thật là buồn, thôi năm sau phục thù cùng anh em</p>
<p>Sau cuộc thi thì mình không viết writeup ngay, đến giờ viết lại thì nội dung không được trọn vẹn, ae thông cảm :)</p>
<h4>Task Magician</h4>
<p>Đây là một task old-school về <code>PHP Type Juggling Vulnerabilities</code>
dạng như:</p>
<div class="highlight"><pre><span></span><code><span class="n">var_dump</span><span class="p">(</span><span class="n">md5</span><span class="p">(</span><span class="s1">'240610708'</span><span class="p">)</span> <span class="o">==</span> <span class="n">md5</span><span class="p">(</span><span class="s1">'QNKCDZO'</span><span class="p">));</span>
</code></pre></div>
<p>Do không sử dụng toán tử <code>===</code> (giống nhau về cả value và type) mà sử dụng <code>==</code> (chỉ cần giống nhau về value), ta hoàn toàn có thể tìm được strings có md5 thỏa mãn điều kiện bài toán.</p>
<p>Tham chiếu thêm tại:
<a href="https://www.netsparker.com/blog/web-security/php-type-juggling-vulnerabilities/">https://www.netsparker.com/blog/web-security/php-type-juggling-vulnerabilities/</a></p>
<h4>Task not_hot_dog</h4>
<p>Task cung cấp cho mình bộ ba <code>n,e,c</code> ta có thể biết ngay đây là một bài attack RSA, với c là bản mã, sau khi giải mã ra sẽ thu được FLAG.</p>
<p>Do khi implement RSA họ sử dụng e lớn, nên sẽ bị ảnh hưởng bởi Wiener attack (đây cũng là dạng old-school RSA trong các giải CTF)
Sử dụng công cụ tại: https://github.com/orisano/owiener để thu được flag.</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">owiener</span>
<span class="o">>>></span> <span class="n">e</span> <span class="o">=</span> <span class="mi">387825392787200906676631198961098070912332865442137539919413714790310139653713077586557654409565459752133439009280843965856789151962860193830258244424149230046832475959852771134503754778007132465468717789936602755336332984790622132641288576440161244396963980583318569320681953570111708877198371377792396775817</span>
<span class="o">>>></span> <span class="n">n</span> <span class="o">=</span> <span class="mi">609983533322177402468580314139090006939877955334245068261469677806169434040069069770928535701086364941983428090933795745853896746458472620457491993499511798536747668197186857850887990812746855062415626715645223089415186093589721763366994454776521466115355580659841153428179997121984448771910872629371808169183</span>
<span class="o">>>></span> <span class="n">d</span> <span class="o">=</span> <span class="n">owiener</span><span class="o">.</span><span class="n">attack</span><span class="p">(</span><span class="n">e</span><span class="p">,</span> <span class="n">n</span><span class="p">)</span>
<span class="o">>>></span> <span class="nb">print</span> <span class="p">(</span><span class="n">d</span><span class="p">)</span>
<span class="mi">40127490441880177477224469176371044914847896019034308382923938039797354608313</span>
</code></pre></div>
<h4>Task Browser preview</h4>
<p><img alt="done" src="http://pp.pymi.vn/images/browser_preview.png"></p>
<p>Với tên và cách setup task này thì có thể thấy đây là một task về bug SSRF (Server-side request forgery)
SSRF như tên gọi thì attacker sẽ khiến cho server thực hiện những request tùy ý (tùy vào từng trường hợp mà các protocol sẽ là http/dns/ftp/smpt/gopher...) như vậy khi có bug SSRF chúng ta sẽ bypass được các bộ filter về source IP - do request đến từ chính server (localhost)</p>
<p>Chức năng của web là nhập vào một URL, ta sẽ preview được nội dung trang web đó.
URL phải validate dạng như sau:</p>
<div class="highlight"><pre><span></span><code> <span class="kd">static</span> <span class="kt">boolean</span> <span class="nf">isUrlValid</span><span class="p">(</span><span class="n">String</span> <span class="n">url</span><span class="p">)</span> <span class="p">{</span>
<span class="n">Pattern</span> <span class="n">domainPattern</span> <span class="o">=</span> <span class="n">Pattern</span><span class="p">.</span><span class="na">compile</span><span class="p">(</span><span class="s">"^https?://[a-z-0-9]+[.][a-z]+.*$"</span><span class="p">,</span> <span class="n">Pattern</span><span class="p">.</span><span class="na">CASE_INSENSITIVE</span><span class="p">);</span>
<span class="n">Matcher</span> <span class="n">matcher</span> <span class="o">=</span> <span class="n">domainPattern</span><span class="p">.</span><span class="na">matcher</span><span class="p">(</span><span class="n">url</span><span class="p">);</span>
<span class="k">return</span> <span class="n">matcher</span><span class="p">.</span><span class="na">find</span><span class="p">();</span>
<span class="p">}</span>
</code></pre></div>
<p>Đọc source thì thấy tiếp server có handler ở port 7654, có thể read flag thông qua phương thức này.</p>
<div class="highlight"><pre><span></span><code><span class="kd">class</span> <span class="nc">DebugServer</span> <span class="p">{</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">run</span><span class="p">()</span> <span class="kd">throws</span> <span class="n">IOException</span> <span class="p">{</span>
<span class="n">HttpServer</span> <span class="n">server</span> <span class="o">=</span> <span class="n">HttpServer</span><span class="p">.</span><span class="na">create</span><span class="p">(</span><span class="k">new</span> <span class="n">InetSocketAddress</span><span class="p">(</span><span class="mi">7654</span><span class="p">),</span> <span class="mi">0</span><span class="p">);</span>
<span class="n">server</span><span class="p">.</span><span class="na">createContext</span><span class="p">(</span><span class="s">"/flag"</span><span class="p">,</span> <span class="k">new</span> <span class="n">FlagHandler</span><span class="p">());</span>
<span class="n">server</span><span class="p">.</span><span class="na">setExecutor</span><span class="p">(</span><span class="kc">null</span><span class="p">);</span>
<span class="n">server</span><span class="p">.</span><span class="na">start</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p>Như vậy ý đồ là quá rõ ràng, mình sẽ nhập vào một URL dạng localhost:7654/flag để get flag, URL làm sao thỏa mãn được bộ filter trên là OK.
Mình sử dụng URL dạng: http://domain.localhost:7654/flag
Có thể kham khảo các cách <code>bypass localhost</code> của hacktrick
https://book.hacktricks.xyz/pentesting-web/ssrf-server-side-request-forgery</p>
<h4>Electronbuzz</h4>
<p>grep nội dung là ra được FLAG.</p>
<h4>Instant flag checker</h4>
<p>Task này mình cũng không còn nhớ rõ đề và script hôm đó viết, chắc hôm đó giải lẹ qua nên viết console luôn, không lưu lại file.</p>
<p>Về ý tưởng của bài này là dạng bruteforce để thu được flag, vậy tại sao để bruteforce được thành công ?</p>
<p>Đó là sử dụng time-based, các giá trị đúng sẽ có response time khác với với các giá trị sai.
Trong những vấn đề mà server không phản hồi cho mình nội dung response (tức là bị blind) thì việc áp dụng kỹ thuật time-based (bắt server thực hiện các heavy task) hoặc sử dụng kỹ thuật outbound (đẩy http request hay dns request ra public server khác) thực sự hữu ý.</p>
<h4>Tổng kết</h4>
<p>Chân thành cảm ơn các anh em @bác Hưng, @đăng hoàng, @Duy Hồ, @ Khang lê hôm đó đã thức đêm làm cùng.
Hy vọng năm sau ae sẽ phục thì dành được rank cao hơn (target top 20)</p>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>.</p>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
</ul>Python lên sao kê chậm - làm web được không?2021-09-11T00:00:00+07:002021-09-11T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2021-09-11:/article/slow/<p>Python chạy chậm hơn các ngôn ngữ lập trình khác nhưng có đủ nhanh cho bạn?</p><p><a href="https://pymi.vn">Pymi.vn</a> là một đơn vị đào tạo Python thực chiến ở Việt Nam,
học xong đi làm luôn. Chúng tôi đưa thẳng vấn đề với học viên
một sự thật về Python mà không hề giấu diếm: Python <strong>chạy</strong> chậm hơn (hầu hết) các
ngôn ngữ lập trình khác. Điều này được đưa vào bài học và học viên được
tự tay đo xem máy của mình tính được bao nhiêu phép +1 trong một giây. Xem kết quả tại: <a href="https://cpu.pymi.vn/">cpu.pymi.vn</a></p>
<p>Kết quả trung bình ở khoảng 15-30 triệu phép cộng / giây với các máy tính Intel i3 i5 i7.
Để làm mốc so sánh, code C tương tự có thể tính khoảng 1 tỷ phép cộng / giây (30-70x).</p>
<p>Đa số người mới đi học Python đều không hề biết chuyện này, họ đi học vì sự hot,
sự đơn giản,
ứng dụng khắp nơi của Python từ AI ML, DeepLearning, Blockchain, ...etc. Điều ấy
cho thấy một phần quan trọng về tính chất của người dùng: chọn ngôn ngữ lập trình
không phải vì nó CHẠY nhanh, họ không tìm học C/C++ hay Fortran.</p>
<p>Trong đó mâu thuẫn hơn cả, là ML/DeepLearning, lĩnh vực cần tính toán cực nhiều,
lại dùng Python phổ biến nhất.
Mâu thuẫn này thường để dành hỏi phỏng vấn các "data scientist" xem có hiểu tại
sao không? Còn với bạn đọc, chúng tôi để dành câu trả lời ở cuối bài.</p>
<p><img alt="fast" src="https://images.unsplash.com/photo-1555980483-93e7b3529e1a?ixlib=rb-1.2.1&q=80&fm=jpg&crop=entropy&cs=tinysrgb&dl=kiril-dobrev-UB0QiVPsXgc-unsplash.jpg&w=800"></p>
<p><center>Photo by <a href="https://unsplash.com/@kirildobrev?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Kiril Dobrev</a> on <a href="https://unsplash.com/s/photos/vietnam-hanoi?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></center></p>
<p>Để so sánh tốc độ, người dùng thực hiện các "benchmark", giải một bài toán cụ thể
và mang các phương án (ngôn ngữ/framework) ra so.</p>
<p>Trang web
<a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/">https://benchmarksgame-team.pages.debian.net/benchmarksgame/</a>
thực hiện giải các bài toán/thuật toán cần tính toán nhiều và đưa ra kết quả.
Python trong bảng so sánh này, chỉ nhanh hơn hai ngôn ngữ phổ biến: Perl và Ruby.</p>
<p><img src="https://benchmarksgame-team.pages.debian.net/benchmarksgame/download/fastest-more.svg" width="800"></p>
<p><a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/box-plot-summary-charts.html">Top 4 đầu bảng: C++, C, Rust, Fortran</a>,
chú ý dòng kết luận quan trọng trên trang viết:</p>
<blockquote>
<p>It's important to be realistic: most people don't care about program performance most of the time.</p>
</blockquote>
<h2>Python trong làm web</h2>
<h3>Trên thế giới</h3>
<p>Trang web viết bằng Python có lượng người truy cập lớn nhất thế giới hiện nay
có lẽ là Instagram.com - mạng xã hội hình ảnh ban đầu là 1 startup, sau đó
đã được Facebook mua lại.</p>
<p>Tại thời điểm viết bài, Instagram.com đứng thứ 21 toàn cầu về lượt truy cập
trên bảng xếp hạng của <a href="https://alexa.com">Alexa</a>, sau Netflix.com, trước Microsoft.com.
Instagram engineer blog về Python tại <a href="https://instagram-engineering.com/tagged/python">https://instagram-engineering.com/tagged/python</a>.</p>
<p>Một số ví dụ khác như <a href="https://eng.uber.com/building-tincup-microservice-implementation/">Uber core dùng Python</a>,
<a href="https://dropbox.tech/tag-results.python">DropBox dùng Python</a>,
hay <a href="https://opensource.googleblog.com/2017/01/grumpy-go-running-python.html">Youtube trước khi Google mua lại dùng Python</a>
phục vụ hàng triệu request mỗi giây.</p>
<blockquote>
<p>The front-end server that drives youtube.com and YouTube’s APIs is primarily
written in Python, and it serves millions of requests per second!</p>
</blockquote>
<h3>Ở Việt Nam</h3>
<p>Tại thời điểm viết bài 2021-09-11, Vnexpress.net là trang web của Việt Nam
đứng thứ #4 VietNam và #396 toàn cầu trên <a href="https://www.alexa.com/topsites/countries/VN">Alexa.com</a>
3 trang top VN là Google, Youtube, Facebook. Nếu xem tiếp top 50 Việt Nam,
các tên hầu hết là các trang báo mạng, hay thương mại điện tử lớn như Shopee,
Lazada, Tiki.
Các trang này viết bằng công nghệ nào không rõ, nhưng rõ ràng là lượt truy cập
đều ít hơn Instagram đáng kể.</p>
<h3>Các đơn vị đo tốc độ trang web</h3>
<h4>Request per seconds (RPS) - throughput</h4>
<p>RPS - có nơi viết là reqs/s, thường là đơn vị chính dùng để đo xem 1 trang web
có thể phục vụ được bao nhiêu yêu cầu mỗi giây. RPS còn có tên khác là <a href="https://www.techempower.com/blog/2016/02/10/think-about-performance-before-building-a-web-application/">"throughput"</a>.
Để bạn đọc hình dung được độ lớn của RPS tại các website thế nào, sau đây là vài ví dụ:</p>
<ul>
<li>Năm 2020, chương trình Rap Việt đạt kỷ lục có lượng người xem cùng lúc <a href="https://tienphong.vn/thuc-hu-cau-chuyen-rap-viet-lap-ki-luc-nguoi-xem-cao-nhat-the-gioi-post1290225.tpo">cao
nhất tại Việt Nam với con số
1.051.000</a>...
cho thấy Youtube chạy ngon lành thế nào.
Đó là đỉnh cao tại Việt Nam, đất nước ~ 100 triệu dân, thì max là 1% xem cùng
một lúc (không ít người ở nước ngoài). Đừng tưởng tượng nước có 100 triệu dân
thì có cái gì 50 triệu người cùng làm một lúc.</li>
<li>6/9/2021, <a href="https://tuoitre.vn/chu-tich-ubnd-tp-hcm-tra-loi-truc-tuyen-vung-xanh-duoc-di-cho-1-tuan-lan-mo-dan-mot-so-dich-vu-20210906184222848.htm">chủ tịch thành phố HCM lên livestream giải đáp thắc mắc của người
dân</a>
, có thể đoán hầu hết chỉ có người dân TPHCM mới xem, lượng view chụp trong ảnh là 74k,
trên tổng <a href="https://tuoitre.vn/toc-do-tiem-vac-xin-o-tp-hcm-dan-tang-cao-da-co-hon-1-trieu-nguoi-tiem-mui-2-20210911085618942.htm">dân số >=18 tuổi TPHCM cỡ khoảng 7.2 triệu người</a> - tại thời điểm hầu hết
người dân đều ở nhà giãn cách xã hội theo chỉ thị 16, cho thấy Facebook livestream ngon lành thế nào.</li>
<li><a href="https://mangadex.dev/mangadex-v5-infrastructure-overview/">mangadex.org với trung bình ~2000 rps</a> - #5,895 trên Alexa.com.
soha.vn - 1 trang báo mạng khá lớn của VCCorp, #50 VN, xếp #5,789 thế giới - gần với mangadex.
Nhaccuatui.vn - trang nghe nhạc lớn nhì, ba tại Việt
Nam, chỉ xếp #14,770. Vậy trừ khi bạn làm tại các công ty có website chạy top
50 VN, bạn mới có 2000 RPS.</li>
<li>2000 RPS => 2000 * 86400 = 172.800.000 requests/day. <strong>172 triệu lượt truy cập</strong>
mỗi ngày. Trang web của bạn/ công ty/ startup của bạn liệu có được 86400 reqs/day? (1 RPS).
Việc có một website với trung bình 2000RPS - top 6000 thế giới, khó hơn nhiều nhiều nhiều
lần so với việc viết code xử lý 2000 RPS.</li>
<li><a href="https://mark.mcnally.je/blog/post/My%20%C2%A34%20a%20month%20server%20can%20handle%204.2%20million%20requests%20a%20day">Một lập trình viên đâu đó ngồi đo</a> thử với 1 cái máy ảo cloud 1CPU 2GB RAM £4 (5.5 USD == 126.000VND).
có thể chạy web Django với 54.3 RPS (54 * 86400 == 4.665.600 reqs/day),
thêm cache sẽ được 63.50RPS, và nếu chỉ có HTML/JS là 180.54 RPS.
Mức giá này khá rẻ, tại <a href="https://bit.ly/dohvn">DigitalOcean</a>,
cấu hình tương đương có giá 10USD/tháng. (Note: link refer, đăng ký bằng link
này được $100 miễn phí cho bạn, $20 cho Pymi.vn).
hay <a href="https://bizflycloud.vn/cloud-server/bang-gia">BIZFLY CLOUD</a> của Việt Nam cũng giá ~200.000.</li>
</ul>
<p>Chú ý: việc tính RPS thường tính trung bình, nhưng trên thực tế, mỗi website có
một "pattern" truy cập khác nhau. Ví dụ tại một vài thời điểm trong ngày, sẽ
có nhiều người truy cập đọc báo hơn là lúc 4h sáng. Các website thương mại điện
tử sẽ luôn "peak" khi chạy các khuyến mãi 6-6, 9-9, 11-11, black friday...
Con số mà các lập trình viên/sysadmin quan tâm là RPS lúc peak, số truy cập
đồng thời cao nhất mà hệ thống chịu được.</p>
<h4>Concurrent User (CCU) - đơn vị mà chủ doanh nghiệp quan tâm</h4>
<p>RPS mang tính chất con số về mặt kỹ thuật, nhưng khi truy cập 1 website, người
dùng thường thực hiện nhiều hơn 1 request. Ví dụ khi truy cập
<a href="https://pymi.vn">https://pymi.vn</a>, trình duyệt
sẽ thực hiện 8 requests (các file HTML/CSS/Javascript/ảnh), còn khi truy cập
vnexpress.net, trình duyệt thực hiện tới 73 requests.</p>
<p>Chủ doanh nghiệp, website, chỉ quan tâm con số mang tính chất kinh tế,
là có bao nhiêu người dang dùng mà không quan tâm có bao nhiêu request, vậy
nên con số Concurrent User (CCU - số người truy cập cùng lúc) ra đời.
Trong ví dụ Rap Việt trên, có 1 triệu người xem cùng lúc - 1 triệu CCU.
1 triệu CCU tương ứng với tối thiểu 1 triệu RPS, nhưng thường RPS cao hơn nhiều lần.</p>
<p>Một điều đáng nói khác, rằng không phải 73 requests đều gọi vào vnexpress.net mà
gọi đến các trang khác chứa ảnh, video...</p>
<p>Khi nói về CCU, chủ doanh nghiệp cũng không quan tâm hệ thống sử dụng bao
nhiêu máy tính ở đây. Nói 1 triệu RPS, không có nghĩa là phải dùng 1 máy,
1 triệu RPS/máy là rất cao, trên thế giới <a href="https://blog.whatsapp.com/1-million-is-so-2011/?lang=en">Whatsapp đã làm được 1m TCP conn từ
2011</a>,
<a href="https://www.phoenixframework.org/blog/the-road-to-2-million-websocket-connections">Elixir Phoenix fullstack web-framework chạy 2 triệu websocket
connections</a>
năm 2015. Nhưng không ai cấm bạn chạy 200 máy sau load-balancer cả.
1.000.000 / 200 = 5.000 RPS.</p>
<p><a href="https://en.wikipedia.org/wiki/C10k_problem">C10k</a> là 1 vấn đề rất năm 2000, để
xử lý 10.000 connection, cùng với sự ra đời của NGINX.</p>
<h4>Response time - latency, p95, p99</h4>
<p>Một đơn vị quan trọng khác khi nói về tốc độ website là <strong>response time</strong> tên
khác là latency - thời
gian tính tức lúc người dùng gửi request đi cho tới khi nhận được nội dung trang web.
Con số này nói đơn giản chính là sự nhanh/chậm của website.
Response time là 10 giây thì người dùng sau khi vào thanh địa chỉ gõ enter, 10
giây sau mới nhìn thấy nội dung trang web.
(để thấy TOÀN BỘ nội dung trang web, người ta dùng thêm đơn vị đo "load time")</p>
<p>Nhưng nếu kết quả response lúc nhanh lúc chậm thì biết lấy số nào? đó là lúc
cần lôi bộ môn <a href="https://medium.com/pymi/d%C3%B9ng-python-%C4%91%E1%BB%83-h%E1%BB%8Dc-th%E1%BB%91ng-k%C3%AA-8e41dfdaaf97">thống
kê</a>
ra để trình bày. Cách đơn giản nhất là lấy trung bình cộng,
nghe thì dễ, nhưng kết quả này thường vô dụng, bởi (99*2) 198 lần load 1s mà 2 lần 901s
thì trung bình là: (99 * 2 + 901 * 2)/200 == 10s.</p>
<p>Cách làm phổ biến là dùng quantile 99 (hay 95) để đo: tức giá trị mà
99% các giá trị khác đều nhỏ hơn hoặc bằng, còn có tên gọi p99 (99th percentile).
Trong ví dụ trên, p99 là 1: <code>int(99/100 * 200 số) = 198</code>, sắp xếp các số tăng
dần thì số thứ 198 có giá trị là 1,
p100 ít khi được nhắc tới, đó là số lớn nhất trong tập.
Xem thêm về quantile và percentile ở phần link tham khảo.</p>
<p>Tại sao lại bỏ đi 1% như vậy? vì kết quả đo đạc có thể có các sai số do nhiều
lý do khác nhau và không có ý nghĩa với vấn đề (chậm do mạng bị cá mập cắn
trong 1 request, chẳng hạn).</p>
<p>Giá trị latency gần 0ms là khủng, 100ms là tốt, 500ms là OK, vài giây là lởm,
nhiều giây là rất không ra gì:</p>
<blockquote>
<p>~0ms is superb; 100ms is good; 500ms is okay; a few seconds is bad; and several seconds may be approaching awful. - www.techempower.com</p>
</blockquote>
<h3>Tốc độ lý thuyết</h3>
<p>Tương tự website benchmark
tốc độ tính toán của các ngôn ngữ, có website benchmark các webframework:
<a href="https://www.techempower.com/benchmarks/#section=data-r20">techempower</a></p>
<p>tại đây, mỗi framework thực hiện 7 loại test và được chấm điểm, đầu bảng hầu hết
là C++ hay Rust, luôn dành chiến thắng về tốc độ, nhưng không mấy ai làm web
site bằng C++ cả.</p>
<blockquote>
<p>Nhớ rằng: Instagram dùng Django.</p>
</blockquote>
<p>Các web framework phổ biến của Python hầu như đều ở phía dưới bảng xếp hạng,
ngang ngửa với Ruby/PHP</p>
<ul>
<li>Django (359/436)</li>
<li>Flask (370/436)</li>
<li>Ruby On Rails (377/436)</li>
<li>PHP Lavarel (388/436)</li>
</ul>
<p>Riêng FastAPI cùng khu với Elixir, NodeJS, Java</p>
<ul>
<li>Go Gin (162/436)</li>
<li>FastAPI (247/436)</li>
<li>Elixir Phoenix (251/436)</li>
<li>NodeJS Express MySQL (287/436)</li>
<li>Java Spring (317/436)</li>
</ul>
<p>vậy kết luận dùng <a href="http://pp.pymi.vn/article/go/">Go</a> chạy nhanh hơn và đập hết Django, Java, Ruby app đi viết
lại ?</p>
<p><strong>Đừng!</strong></p>
<p>Kết quả của việc benchmark rất khó để đưa ra kết luận, và tốc độ thực sự phụ
thuộc vào bài toán cụ thể chứ không phải dựa và kết luận "hello world" của
framework.</p>
<p>Dù điểm trung bình của Go + Gin framework (nhanh) hơn của FastAPI, nhưng
khi xem từng test cụ thể, như test data update (có sự ảnh hưởng lớn của database)
thì FastAPI bỏ xa xa xa Go + Gin. Website của bạn có update DB không? hầu hết là có, tùy trường
hợp. Khi chỉ đơn thuần tính toán và không động vào DB, Go rõ ràng dành chiến thắng. Xem thêm phần <a href="https://github.com/tiangolo/fastapi/issues/1664#issuecomment-653580642">thắc mắc về tốc độ của FastAPI tại đây</a>.</p>
<p>Các kết quả benchmark dễ dàng bị đảo ngược chỉ với vài thay đổi phụ thuộc bài toán, như
trường hợp benchmark Flask sau:</p>
<ul>
<li><a href="https://calpaterson.com/async-python-is-not-faster.html">bài này benchmark kết luận async Python không nhanh hơn sync</a></li>
<li><a href="https://blog.miguelgrinberg.com/post/ignore-all-web-performance-benchmarks-including-this-one">bài này chỉnh sửa lại 1 chút, và cho kết quả khác hoàn toàn</a></li>
</ul>
<p>Tác giả <a href="https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-i-hello-world">Flask mega tutorial Miguel Grinberg</a> chỉ đưa ra kết luận cuối cùng với tên bài viết:</p>
<blockquote>
<p>Ignore All Web Performance Benchmarks, Including This One</p>
</blockquote>
<p>Một bài viết khác thực hiện cải thiện website dùng <a href="https://suade.org/dev/12-requests-per-second-with-python/">Flask + SQLAlchemy ORM từ 12RPS thành 75RPS</a>.</p>
<p>Trong các trang web, tốc độ của website phần lớn phụ thuộc vào database,
kiến trúc, cache, hơn là phụ thuộc vào tốc độ của web framework.</p>
<h3>Load test Python</h3>
<p>Cách để rút ra được kết quả chuẩn nhất, có ý nghĩa nhất là tự đo code của mình.
Công cụ để benchmark web site:</p>
<ul>
<li>ab (Apache benchmark tool) <code>sudo apt-get install apache2-utils</code></li>
<li><a href="https://github.com/rakyll/hey">Go hey</a></li>
<li><a href="https://github.com/wg/wrk">C wrk</a></li>
<li><a href="https://locust.io/">Python Locust.io + webUI + distributed + graph report</a></li>
</ul>
<p>Thử benchmark code Flask+Gunicorn hello world, 1000 CCU:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="vm">__name__</span><span class="p">)</span>
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/fast"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">fast</span><span class="p">():</span>
<span class="k">return</span> <span class="s2">"yes"</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code>$ gunicorn app:app -w <span class="m">9</span> -k gthread
</code></pre></div>
<p>Rồi chạy <code>wrk</code>:</p>
<div class="highlight"><pre><span></span><code>$ ./wrk -c <span class="m">1000</span> -d 10s http://localhost:8000/fast
Running 10s <span class="nb">test</span> @ http://localhost:8000/fast
<span class="m">2</span> threads and <span class="m">1000</span> connections
Thread Stats Avg Stdev Max +/- Stdev
Latency <span class="m">233</span>.14ms <span class="m">61</span>.78ms <span class="m">371</span>.49ms <span class="m">68</span>.27%
Req/Sec <span class="m">2</span>.13k <span class="m">222</span>.09 <span class="m">2</span>.75k <span class="m">67</span>.50%
<span class="m">42372</span> requests <span class="k">in</span> <span class="m">10</span>.08s, <span class="m">6</span>.47MB <span class="nb">read</span>
Requests/sec: <span class="m">4202</span>.64
Transfer/sec: <span class="m">656</span>.75KB
</code></pre></div>
<p>4200rps với latency avg 200ms, max cũng chỉ là 371ms.</p>
<p>hay <code>ab</code>:</p>
<div class="highlight"><pre><span></span><code>$ ab -n <span class="m">10000</span> -c <span class="m">1000</span> http://localhost:8000/fast
Concurrency Level: <span class="m">1000</span>
Requests per second: <span class="m">3706</span>.73 <span class="o">[</span><span class="c1">#/sec] (mean)</span>
Percentage of the requests served within a certain <span class="nb">time</span> <span class="o">(</span>ms<span class="o">)</span>
<span class="m">50</span>% <span class="m">264</span>
<span class="m">66</span>% <span class="m">293</span>
<span class="m">75</span>% <span class="m">312</span>
<span class="m">80</span>% <span class="m">327</span>
<span class="m">90</span>% <span class="m">360</span>
<span class="m">95</span>% <span class="m">397</span>
<span class="m">98</span>% <span class="m">426</span>
<span class="m">99</span>% <span class="m">448</span>
<span class="m">100</span>% <span class="m">476</span> <span class="o">(</span>longest request<span class="o">)</span>
</code></pre></div>
<p>Test trên</p>
<div class="highlight"><pre><span></span><code>AMD Ryzen <span class="m">3</span> 4300U - <span class="m">4</span> cores - 8GB RAM
$ lsb_release -d<span class="p">;</span> python3 --version
Description: Ubuntu <span class="m">20</span>.04.3 LTS
Python <span class="m">3</span>.8.10
Flask <span class="m">2</span>.0.1
gunicorn <span class="m">20</span>.1.0
$ ./wrk --version
wrk a211dd5 <span class="o">[</span>epoll<span class="o">]</span> Copyright <span class="o">(</span>C<span class="o">)</span> <span class="m">2012</span> Will Glozer
</code></pre></div>
<h2>Python trong hệ thống</h2>
<p>Vài trường hợp minh họa code Python viết rất nhanh và chạy đủ nhanh:</p>
<ul>
<li><a href="https://pp.pymi.vn/article/10x/">Khi sử dụng Kafka trong bài tăng tốc độ
10x</a>, dùng thư viện
https://github.com/confluentinc/confluent-kafka-python, bên dưới thực chất gọi code C librdkafka,
thay Python bằng code Go <a href="https://github.com/confluentinc/confluent-kafka-go/issues/490#issuecomment-655339047">không làm nhanh
hơn</a>,
<a href="https://github.com/confluentinc/confluent-kafka-go/issues/567">thậm chí còn chậm
hơn</a>, giải pháp
có thể là
tìm một thư viện Kafka viết bằng Go rồi benchmark lại.</li>
<li>Code C++ thường được coi là <strong>hiển nhiên</strong> nhanh hơn Python, <a href="https://stackoverflow.com/questions/9371238/why-is-reading-lines-from-stdin-much-slower-in-c-than-python?rq=1">nhưng phải biết
bật tắt đúng thứ không sẽ chậm hơn</a></li>
<li><a href="https://stackoverflow.com/questions/14205096/c11-regex-slower-than-python">c++11 regex slower than python - và nhớ rằng tuy nói là Python, nhưng rất nhiều chỗ của Python thực chất gọi code C bên dưới</a></li>
</ul>
<h2>Python trong xử lý data</h2>
<p>Các thư viện tính toán khoa học trong Python đều sử dụng các ngôn ngữ tính toán
nhanh bên dưới, như Numpy/Pandas dùng C, PyTorch/Tensorflow dùng C++. Kết hợp
với cú pháp Python ở trên, tạo nên sự hoàn hảo của cả 2 thế giới: code đẹp, đơn
giản của Python, và tính nhanh (gần) như C/C++/Fortran.</p>
<h2>Kết luận</h2>
<p>Đọc kết quả benchmark trên mạng rồi đưa ra kết luận là một việc làm rất nguy hiểm,
nên tự học cách benchmark cho trường hợp cụ thể của mình.</p>
<p>Tốc độ của ngôn ngữ lập trình & framework trên thực tế thường không quan trọng
bằng: tốc độ phát triển tính năng/ dự án, chi phí bỏ ra để viết lại bằng
một ngôn ngữ X chạy nhanh hơn.</p>
<h2>References</h2>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
<li><a href="https://www.techempower.com/blog/2016/02/10/think-about-performance-before-building-a-web-application/">techempower - Think about Performance Before Building a Web Application</a></li>
<li><a href="https://www.pingdom.com/blog/page-load-time-vs-response-time-what-is-the-difference/">page load time vs response time</a></li>
<li><a href="https://en.wikipedia.org/wiki/Quantile">Quantile</a></li>
<li><a href="https://en.wikipedia.org/wiki/Percentile">Percentile</a></li>
<li><a href="https://github.com/tiangolo/fastapi/issues/1664#issuecomment-653580642">FastAPI is fast</a></li>
<li><a href="https://stackoverflow.com/questions/14205096/c11-regex-slower-than-python">c11-regex-slower-than-python</a></li>
<li><a href="https://stackoverflow.com/questions/9371238/why-is-reading-lines-from-stdin-much-slower-in-c-than-python?rq=1">why-is-reading-lines-from-stdin-much-slower-in-c-than-python</a></li>
</ul>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a></p>Học Vật Lý với Python2021-07-24T00:00:00+07:002021-07-24T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2021-07-24:/article/vatlipymi/<p>Ôn tập môn điện dùng MicroPython trên ESP8266</p><p>Trong không khí toàn dân ở nhà ôn tập môn Vật Lý vào năm Covid-19 thứ 2,
một "thầy giáo Python" cũng không thể giấu nổi tình yêu với môn học của tự nhiên
này và quyết chuyển sang mở lớp ôn tập môn điện.</p>
<p>Sau 30 phút chăm chỉ và tập trung, các học viên sẽ có thể thắp sáng 1 cái
đèn (LED) sử dụng MicroPython trên vi xử lý ESP8266.</p>
<p><img alt="hi" src="http://pp.pymi.vn/images/bulb.jpg"></p>
<p>Trong 3 môn học "tự nhiên" của trường phổ thông, Toán được cho là "công thức",
"trừu tượng", "khô khan",
thì Lý, Hóa lẽ ra phải gần hơn với thực hành.
Ở các trường học "dưới xuôi" có điều kiện, được thực hành trong phòng thí nghiệm,
chơi với mô hình,
thì với học sinh miền núi năm 200X chúng tôi, thứ thực hành duy nhất trong
môn Hóa là giấy quỳ tím thả vào axit chuyển sang đỏ,
còn môn Lý chắc là tự bật cái công tắc bóng đèn nhà mình.</p>
<p>Cảnh báo: tác giả không có bằng cấp sư phạm môn Vật Lý, cũng không phải kỹ sư
điện.</p>
<p>Vì là giờ học thực hành, nên học viên cố gắng kiếm thiết bị để làm theo nhé.</p>
<h2>Chuẩn bị dụng cụ</h2>
<ul>
<li>Một thiết bị vi xử lý ESP 8266 (mức giá < 60.000 VND). Ví dụ <a href="https://shopee.vn/M%E1%BA%A1ch-Thu-Ph%C3%A1t-WiFi-ESP8266-Wemos-D1-mini-(NodeMCU-Mini-D1)-c%C3%B3-k%C3%A8m-b%E1%BB%99-Jump-c%E1%BA%AFm-i.106323333.1791339648?position=11">Wemos D1 Mini</a></li>
<li>Máy tính có Python (tốt nhất là dùng hệ điều hành Ubuntu hay MacOS)</li>
<li>1 Điện trở 220 Ohm (1000 VND?)</li>
<li>2 dây nối (1000 VND?)</li>
<li>1 đèn LED (1000 VND?)</li>
<li>(Khuyến khích - cho đơn giản) hay mua một bộ kit cơ bản gồm cả 3 thứ trên với
<a href="https://www.cytrontech.vn/p-raspberry-pi-pico-basic-kit-without-pico">giá 95.000 VND</a>.</li>
</ul>
<h2>Cài đặt MicroPython lên ESP8266</h2>
<p>Làm theo hướng dẫn trong bài <a href="http://pp.pymi.vn/article/micropython/">Dùng MicroPython với wifi board ESP-8266</a>.</p>
<h2>Mạch điện</h2>
<p>Hình vẽ sử dụng <a href="https://www.circuit-diagram.org/editor/">circuit-diagram</a></p>
<p><img src="http://pp.pymi.vn/images/circuit.svg"></p>
<p><img src="http://pp.pymi.vn/images/circuit_h.jpg" width=500>
<img src="http://pp.pymi.vn/images/circuit_topdown.jpg" width=300></p>
<h2>Nối mạch</h2>
<ul>
<li>Cắm 1 dây vào pin ghi chữ G của ESP8266, nối đầu dây còn lại vào 1 đầu của điện trở</li>
<li>chân còn lại của điện trở nối vào chân ngắn của đèn LED</li>
<li>chân dài của đèn LED nối vào pin D5.</li>
<li>cắm điện cổng USB cho ESP8266.</li>
<li>bật terminal, kết nối vào ESP8266 qua câu lệnh picocom:</li>
</ul>
<div class="highlight"><pre><span></span><code>sudo picocom /dev/ttyUSB0 --baud 115200
</code></pre></div>
<p>Gõ code Python</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">machine</span>
<span class="o">>>></span> <span class="n">machine</span><span class="o">.</span><span class="n">freq</span><span class="p">()</span>
<span class="mi">80000000</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">machine</span> <span class="kn">import</span> <span class="n">Pin</span>
<span class="o">>>></span> <span class="n">led</span> <span class="o">=</span> <span class="n">Pin</span><span class="p">(</span><span class="mi">14</span><span class="p">,</span> <span class="n">Pin</span><span class="o">.</span><span class="n">OUT</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">led</span><span class="o">.</span><span class="n">on</span><span class="p">()</span>
<span class="o">>>></span> <span class="n">led</span><span class="o">.</span><span class="n">off</span><span class="p">()</span>
</code></pre></div>
<p>Sẽ thấy đèn LED sáng sau khi <code>led.on()</code> rồi tắt sau <code>led.off()</code>.</p>
<p><img src="http://pp.pymi.vn/images/circuit_on.webp" width=300></p>
<h2>Pin là gì</h2>
<p><img src="https://www.wemos.cc/en/latest/_images/d1_mini_v3.1.0_1_16x16.jpg" width=300></p>
<p>16 ô tròn nhỏ trong hình là 16 pin của mạch Wemos D1 Mini.</p>
<p>Pin là điểm tiếp xúc kim loại, là nơi để vi xử lý tương tác với bên ngoài (như đọc/ghi "dữ liệu",...)
bật tắt bóng đèn, đọc thông tin từ các sensor (cảm biến) như đo nhiệt độ, ...</p>
<p>Mỗi Pin có chức năng riêng, để biết mỗi mạch có pin nào làm gì, cần tìm tài liệu
của mạch đó (thường ở trang chủ) hoặc trong tài liệu kèm khi bán.
Theo <a href="https://www.wemos.cc/en/latest/d1/d1_mini.html">wemos.cc</a></p>
<div class="highlight"><pre><span></span><code><span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| Pin | Function | ESP</span><span class="nb">-</span><span class="c">8266 Pin |</span>
<span class="nb">+</span><span class="c">======</span><span class="nb">+</span><span class="c">==============================</span><span class="nb">+</span><span class="c">==============</span><span class="nb">+</span><span class="c"></span>
<span class="c">| TX | TXD | TXD |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| RX | RXD | RXD |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| A0 | Analog input</span><span class="nt">,</span><span class="c"> max 3</span><span class="nt">.</span><span class="c">2V | A0 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D0 | IO | GPIO16 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D1 | IO</span><span class="nt">,</span><span class="c"> SCL | GPIO5 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D2 | IO</span><span class="nt">,</span><span class="c"> SDA | GPIO4 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D3 | IO</span><span class="nt">,</span><span class="c"> 10k Pull</span><span class="nb">-</span><span class="c">up | GPIO0 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D4 | IO</span><span class="nt">,</span><span class="c"> 10k Pull</span><span class="nb">-</span><span class="c">up</span><span class="nt">,</span><span class="c"> BUILTIN_LED | GPIO2 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D5 | IO</span><span class="nt">,</span><span class="c"> SCK | GPIO14 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D6 | IO</span><span class="nt">,</span><span class="c"> MISO | GPIO12 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D7 | IO</span><span class="nt">,</span><span class="c"> MOSI | GPIO13 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| D8 | IO</span><span class="nt">,</span><span class="c"> 10k Pull</span><span class="nb">-</span><span class="c">down</span><span class="nt">,</span><span class="c"> SS | GPIO15 |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| G | Ground | GND |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| 5V | 5V | \</span><span class="nb">-</span><span class="c"> |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| 3V3 | 3</span><span class="nt">.</span><span class="c">3V | 3</span><span class="nt">.</span><span class="c">3V |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
<span class="c">| RST | Reset | RST |</span>
<span class="nb">+------+------------------------------+--------------+</span><span class="c"></span>
</code></pre></div>
<h2>GPIO là gì</h2>
<p>General-Purpose Input/Output (GPIO) là khái niệm về mặt logic chứ không phải vật
lý như pin nói trên.</p>
<p>Trong 16 pin của Wemos D1 mini, chỉ có 11 pin được gọi là digital pin D0-D8, G,
RX, TX tương ứng với các GPIO pin.
Chú ý cách đánh số khác nhau giữa pin vật lý (D5) so với GPIO pin (GPIO14),
chúng không có quy luật, để biết chính xác cần tra tài liệu cho từng mạch.</p>
<p>Khi lập trình điều khiển các GPIO pin, có thể đổi chúng ở 2 trạng thái:</p>
<ul>
<li>high (on - 1)</li>
<li>low (off - 0)</li>
</ul>
<p>Trên Wemos D1 mini, các digital pin đều có điện thế 3.3V khi high, 0V khi low.</p>
<p>Pinout là tài liệu liệt kê nối pin trên mạch ứng với GPIO pin nào.</p>
<h2>Resistor - điện trở</h2>
<p>Resistor /rɪˈzɪstə/ điện trở, là thứ cản trở dòng điện. Trong bài này dùng để giảm
hiệu điện thế trước khi gặp đèn LED - tránh cháy nổ. Trên
resistor thường có 4 vạch màu (color band) trở lên với 10 màu để ký hiệu nó có
điện trở bao nhiêu Ω (đơn vị Ohm. Việt sub: ôm).</p>
<h3>Cách đọc vạch màu trên điện trở</h3>
<p>Hai vạch đầu đổi trực tiếp ra số, vạch tiếp theo là hệ số nhân, và vạch cuối cùng là
khả năng chịu đựng sai lệch tính theo phần trăm.</p>
<p>Trong bài, sử dụng "đỏ đỏ nâu vàng":</p>
<ul>
<li>đỏ(2) đỏ(2) nâu(x10) = 22 * 10 = 220Ω</li>
<li>vàng = +-5%</li>
</ul>
<p>Cách nhớ các màu theo tiếng Anh:</p>
<blockquote>
<p>Bad Beer Rots Out Your Guts But Vodka Goes Well – Get Some Now.</p>
</blockquote>
<ul>
<li>Bad - Black: 0</li>
<li>Beer - Brown: 1</li>
<li>Rots - Red: 2</li>
<li>Out - Orange: 3</li>
<li>Your - Yellow: 4</li>
<li>Guts - Green: 5</li>
<li>But - Blue: 6</li>
<li>Vodka - Violet: 7</li>
<li>Goes - Grey: 8</li>
<li>Well - White: 9</li>
</ul>
<p><img src="http://pp.pymi.vn/images/resistors.jpg" width=600></p>
<h2>LED là gì</h2>
<p>Light-Emitting Diode (LED) - là một Diode (Việt sub: "đi ốt") có khả năng phát
ra ánh sáng. Trong tiếng Việt thường hay gọi là lét, nhưng trong tiếng Anh,
người ta vẫn đọc từng chữ cái L-E-D (Việt sub: eo-l i đi).</p>
<p>LED phát ra ánh sáng nhờ việc electron
trong semiconductor (chất bán dẫn) kết hợp với các hố electron (nguyên tử mà bị
thiếu electron) sinh ra năng lượng ở dạng photon ánh sáng. Được phát minh từ
<a href="https://en.wikipedia.org/wiki/Light-emitting_diode">1907 nhưng mãi tới 1962</a>
mới được đưa vào sản xuất. Ngày nay LED là công nghệ phát sáng rất quan
trọng được dùng phổ biến để làm bóng đèn,</p>
<p><a href="https://www.nobelprize.org/prizes/physics/2014/summary/">Năm 2014, giải Nobel Vật Lý được trao cho nhóm phát minh ra LED ánh sáng xanh lam (blue)</a>.</p>
<h3>Các loại bóng đèn</h3>
<ul>
<li>Bóng đèn dây tóc - loại bóng đèn đầu tiên trên thế giới, được phát minh đồng thời
<a href="https://en.wikipedia.org/wiki/Edison_light_bulb">bởi Joseph Swan và Edison năm 1879</a>,
dùng điện làm nóng dây tóc dẫn tới phát sáng.</li>
<li>Đèn tuýp / đèn huỳnh quang (Fluorescent Lamp): đèn tuýp tên tiếng Anh là
Tubelight - nói đến hình dạng cái ống của nó. Nếu ngày nay
dịch như ngày xưa, ta sẽ lên <code>Diu Tuýp</code> để xem clip. Loại đèn này sử dụng bột
phốt pho để phát sáng khi có các ánh sáng cực tím va vào. Ánh sáng cực tím
được tạo ra bằng cách phóng điện vào khí trơ (như Argon/Neon) làm thủy ngân bay hơi.
Đèn huỳnh quang có chứa thủy ngân, rất độc hại khi thải ra môi trường.</li>
<li>Bóng đèn compact: compact tiếng Anh nghĩa là thu gọn/chặt lại. Cái tên được
giữ 1 phần gốc tiếng Anh, tên đầy đủ là compact fluorescent lamp (CFL). Về
mặt nguyên lý vẫn là đèn huỳnh quang, nhưng nhỏ gọn, cho hiệu năng cao hơn,
hay tiết kiệm điện hơn.</li>
<li>Bóng đèn LED: bóng đèn sử dụng các LED để tạo ánh sáng trắng thay các công
nghệ nói trên. Đây là công nghệ hiện đại nhất ngày này: tiết kiệm, hiệu quả,
ít hại môi trường. LED là công nghệ, nó có thể thay thế cho đèn huỳnh quang và
đóng vai 1 bóng đèn tuýp, hay thay đèn dây tóc như bóng đèn tròn.</li>
</ul>
<p>LED thường có 2 chân, chân dài là anode (đầu dương +), và chân ngắn là cathode
(đầu âm -).</p>
<h2>Điện hoạt động thế nào ở đây</h2>
<p><img src="http://pp.pymi.vn/images/circuit.svg"></p>
<p>Khi code <code>led.on()</code> chạy xong, ESP8266 đặt hiệu điện thế 3.3V tại GPIO14 (Pin D5),
dòng điện chạy qua chân + của LED, qua chân - tới điện trở rồi theo dây nối
tới pin G (ground - nối đất, điện thế là ~0V). Chiều dòng điện ngược với chiều của electron.
Các electron chạy từ G qua điện trở R, giảm điện thế hai đầu LED, election qua
LED làm phát sáng LED, rồi tới GPIO14.</p>
<h2>Các câu hỏi vật lý</h2>
<h3>Voltage - hiệu điện thế</h3>
<p>Hiệu điện thế (hay điện áp) - ký hiệu ở Việt Nam là U, ký hiệu bên tây là V.
Đơn vị: <strong>volt</strong> - ký hiệu <strong>V</strong>.</p>
<p>Hiệu là kết quả của phép trừ, ở đây chỉ độ chênh lệch về điện thế (electric potential).
Có thể tưởng tượng hiệu điện thế giống như 1 loại áp lực.</p>
<h3>Current - cường độ dòng điện</h3>
<p>Cường độ dòng điện - ký hiệu là I.
Đơn vị: <strong>ampere</strong> - ký hiệu <strong>A</strong>.</p>
<p>Cường độ là độ mạnh của dòng điện.</p>
<h3>Ohm's law - định luật Ohm</h3>
<p>Khi qua vật dẫn điện, hiệu điện thế và cường độ dòng điện tỷ lệ thuận với nhau.</p>
<div class="highlight"><pre><span></span><code>I = V/R
</code></pre></div>
<p>hay</p>
<div class="highlight"><pre><span></span><code>R = V/I
</code></pre></div>
<p>tỷ lệ này gọi là "resistance" (điện trở) của vật dẫn điện, ký hiệu chữ R
là từ đây ra.</p>
<div class="highlight"><pre><span></span><code><span class="mf">1</span> <span class="n">Ohm</span> <span class="o">=</span> <span class="mf">1</span> <span class="n">Volt</span> <span class="o">/</span> <span class="mf">1</span> <span class="n">Ampere</span><span class="mf">.</span>
</code></pre></div>
<p>Thứ khái niệm kiểu tỷ lệ như này khá phổ biến trong Vật Lý, như</p>
<div class="highlight"><pre><span></span><code>vận tốc = quãng đường / thời gian
1 m/s = 1m / 1s
</code></pre></div>
<p>Ba khái niệm này có thể hình dung như một thác nước chảy từ trên cao xuống (U),
va vào đá giữa dòng (R) mà giảm tốc độ (I).</p>
<p>Trong điện dùng hàng ngày, các thiết bị điện hầu hết có điện trở R cố định,
nên khi U lớn thì I cũng sẽ lớn theo (tỷ lệ thuận).</p>
<h3>Công thức</h3>
<p>Trong mạch mắc nối tiếp, I không đổi tại mọi điểm.</p>
<div class="highlight"><pre><span></span><code>V = I*R + I*R_led
V = V1 + I*R_led
</code></pre></div>
<p>Như vậy việc cho điện trở 220 Ohm vào nhằm giảm hiệu điện thế cho LED.
Cụ thể:</p>
<div class="highlight"><pre><span></span><code><span class="mf">3.3</span><span class="n">V</span> <span class="o">=</span> <span class="n">I</span><span class="o">*</span><span class="mf">220</span> <span class="o">+</span> <span class="n">I</span><span class="o">*</span><span class="n">R_led</span>
</code></pre></div>
<h3>Không dùng điện trở có sao không?</h3>
<p>Cháy LED.</p>
<p>Thứ phá hỏng thiết bị điện khi cắm nhầm thiết bị 110V vào điện 220V là do hiệu
điện thế (như 1 loại áp lực) của dòng cao hơn của thiết bị có thể chịu được.
Cường độ dòng điện không phải là nguyên nhân.</p>
<h3>Điện trong nhà mắc nối tiếp hay song song</h3>
<p>Song song.</p>
<p>Khi mắc song song, V không đổi tại mọi điểm.
V là thứ quan trọng nhất khi nói về dòng điện, vậy nên các thiết bị trong nhà
nối song song để có V như nhau - như được cấp (220V).
Thiết bị này không bị tụt áp V khi dùng thiết bị khác.</p>
<h3>Đấu ngược chân LED có sao không?</h3>
<p>LED không chịu được voltage ngược lớn, nên cũng có thể sẽ cháy.</p>
<p>PS: "cháy" thường là hiện tượng LED nóng và hỏng, có thể có ít khói, chứ hiếm mà
có cả lửa.</p>
<h2>Kết luận</h2>
<p>Việc học tập, ngoài mục tiêu thu nhận "kiến thức", thì phải vui, không vui học
làm sao vào?
Ngày nay, người học đi học như gánh nặng, người dạy đi dạy như cho xong, trả
bài. Chẳng còn thấy mấy ai nói "đi học cho vui", "vui được đi học" nữa.</p>
<p>Nếu ngày càng có nhiều giáo viên mang lại hứng thú với môn Vật Lý như trên
mạng những ngày qua, tôi tin rằng một ngày không xa, ở Việt Nam, người người
nói về Vật Lý, ăn Vật Lý, ngủ Vật Lý (như người Nga trong <a href="https://www.imdb.com/title/tt10048342/">The Queen's
Gambit</a> mê
chơi cờ vua) thì chẳng bao lâu nữa, Việt Nam sẽ trở thành một cường quốc Vật Lý,
<a href="https://vnexpress.net/viet-nam-gianh-ba-huy-chuong-vang-olympic-vat-ly-quoc-te-4329711.html">giỏi cả lý
thuyết</a>
lẫn thực hành như trên các khẩu hiệu. Phóng lên trời, bay qua
Sao Hỏa, bỏ lại xa đằng sau cả Mỹ lẫn Tàu...</p>
<h2>Đọc bài viết tôi không thấy vui</h2>
<p>Vì là môn thực hành, nên bạn phải tận tay cắm mạch, thấy bóng đèn sáng, làm cháy
LED khói mù mịt nổ văng tung tóe mới cảm nhận được cảm giác thỏa mãn này.</p>
<p>Khó có thể có cảm giác gì khi đọc "lý thuyết đánh đàn" hay "các bước nấu bún
giả cầy".</p>
<h2>Học thêm</h2>
<p>Nếu có đam mê về phần cơ học, con lắc, điện xoay chiều, vũ trụ, lượng tử, ...
hay tất tần tật Vật Lý phổ thông, hãy tham gia lớp học miễn phí của
cô <a href="https://www.facebook.com/VatliMinhThu/">VatliMinhThu</a> nhé!</p>
<h2>Tham khảo</h2>
<ul>
<li>https://electronics.stackexchange.com/a/20689</li>
<li>https://www.raspberrypi.org/documentation/usage/gpio/</li>
<li>https://www.arduino.cc/en/reference/board</li>
<li><a href="https://en.wikipedia.org/wiki/Electronic_color_code#Resistor_code">Color band system</a></li>
<li><a href="https://vi.wikipedia.org/wiki/M%E1%BA%A1ch_n%E1%BB%91i_ti%E1%BA%BFp_v%C3%A0_song_song">Mạch nối tiếp và song song</a></li>
</ul>
<h2>Hết</h2>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>.</p>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
</ul>10x engineer - cắt giảm chi phí 10 lần2021-06-30T00:00:00+07:002021-06-30T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2021-06-30:/article/10x/<p>với regex, lambda, kafka - nhờ học hành Python tử tế</p><p>10x engineer là một thần thoại (myth) lâu đời trong giới IT, mơ tưởng về
1 developer có khả năng code "hơn" người bình thường 10 lần.
Vì là "myth", nên có người tin, có người không.</p>
<p><a href="https://youtu.be/QoVeAANfESY"><img alt="The Myth" src="https://img.youtube.com/vi/QoVeAANfESY/0.jpg"></a></p>
<h2>Thần thoại 3x 4x 5x ... 10x (xxxxxxxxxx)</h2>
<p>Việc lên internet tìm kiếm 10x engineer trên thế giới không quá khó khăn, nhưng
gặp khi đi làm ngoài thực tế là chuyện không nhiều.
10x thế giới tạm kể:</p>
<ul>
<li><a href="https://en.wikipedia.org/wiki/Linus_Torvalds">Linus Torvard</a> - tác giả Linux, git</li>
<li><a href="https://en.wikipedia.org/wiki/Fabrice_Bellard">Fabrice Bellard</a> - tác giả FFmpeg, QEMU, tiny C compiler (tcc), quickJS</li>
<li><a href="https://en.wikipedia.org/wiki/John_Carmack">John Carmack</a> - tác giả game Doom, Quake</li>
<li><a href="https://norvig.com/">Peter Norvig</a> - Director of Research Google</li>
<li><a href="http://antirez.com/latest/0">Antirez</a> - tác giả redis, hping</li>
</ul>
<p>những ví dụ trên chỉ phục vụ mục đích dễ hình dung, bởi họ thuộc cỡ 100 hay 1000x
chứ không phải 10x.
Bí quyết là gì không rõ, nhưng điểm chung: họ đều đã ngoài 40 và dành hơn nửa
cuộc đời làm software. Không có ai 30 đã về nghỉ hay lên làm manager cả.</p>
<p>Tiêu chí 10x không rõ ràng, vì đây là "myth", nên mỗi người nghĩ theo
1 kiểu. Theo một tiêu chí ví dụ, mrX gõ nhanh hơn mrY 10 lần, nên cũng có khi
được gọi là 10x engineer.</p>
<p>Bài viết này không liên quan tới chuyện các 10x nói trên, mà đơn giản chỉ là
tiết kiệm 10x chi phí chạy code Python, nhờ được học Python "tử tế".</p>
<h2>Ví dụ</h2>
<p>Sinh ra 1 file .log.gz chứa 1_500_000 dòng (từ 3 dòng lặp đi lặp lại)</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">gzip</span>
<span class="n">lines</span> <span class="o">=</span> <span class="p">[</span>
<span class="s1">'http 2015-05-13T23:39:43.945958Z my-loadbalancer 192.168.131.39:2817 10.0.0.1:80 0.000073 0.001048 0.000057 200 200 0 29 "GET http://www.example.com:80/index HTTP/1.1" "curl/7.38.0" - - arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-targets/73e2d6bc24d8a067 "Root=1-58337262-36d228ad5d99923122bbe354"'</span><span class="p">,</span>
<span class="s1">'https 2015-05-13T23:39:43.945958Z my-loadbalancer 192.168.131.39:2817 10.0.0.1:80 0.000086 0.001048 0.001337 200 200 0 57 "GET https://mytest-111.ap-northeast-1.elb.amazonaws.com:443/p/a/t/h?foo=bar&hoge=fuga HTTP/1.1" "curl/7.38.0" DHE-RSA-AES128-SHA TLSv1.2 arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-targets/73e2d6bc24d8a067 "Root=1-58337262-36d228ad5d99923122bbe354"'</span><span class="p">,</span>
<span class="s1">'https 2015-05-13T23:39:43.945958Z my-loadbalancer 192.168.131.39:2817 10.0.0.1:80 0.000086 0.001048 0.001337 200 200 0 57 "GET https://mytest-111.ap-northeast-1.elb.amazonaws.com:443/p/a/t/h?foo=bar&hoge=fuga:904abc HTTP/1.1" "curl/7.38.0" DHE-RSA-AES128-SHA TLSv1.2 arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-targets/73e2d6bc24d8a067 "Root=1-58337262-36d228ad5d99923122bbe354"'</span><span class="p">,</span>
<span class="p">]</span>
<span class="k">with</span> <span class="n">gzip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">"bigfile.log.gz"</span><span class="p">,</span> <span class="s2">"wb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">fout</span><span class="p">:</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">i</span> <span class="o"><</span> <span class="mi">1_500_000</span><span class="p">:</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">lines</span><span class="p">[</span><span class="n">i</span> <span class="o">%</span> <span class="nb">len</span><span class="p">(</span><span class="n">lines</span><span class="p">)]</span> <span class="o">+</span> <span class="s2">"</span><span class="se">\n</span><span class="s2">"</span>
<span class="n">fout</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">line</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">))</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
</code></pre></div>
<p>File này có kích thước khá nhỏ (<10MB)</p>
<div class="highlight"><pre><span></span><code>$ python makelog.py<span class="p">;</span> ls -la bigfile.log.gz
-rw-rw-r-- <span class="m">1</span> hvn hvn <span class="m">2960897</span> Jun <span class="m">30</span> <span class="m">22</span>:42 bigfile.log.gz
$ gunzip bigfile.log.gz<span class="p">;</span> wc -l bigfile.log<span class="p">;</span> ls -l bigfile.log
<span class="m">1500000</span> bigfile.log
-rw-rw-r-- <span class="m">1</span> hvn hvn <span class="m">559000000</span> Jun <span class="m">30</span> <span class="m">22</span>:42 bigfile.log
$ ls -lh bigfile.log
-rw-rw-r-- <span class="m">1</span> hvn hvn 534M Jun <span class="m">30</span> <span class="m">22</span>:42 bigfile.log
</code></pre></div>
<p>nhưng khi giải nén, kích thước lớn hơn rất nhiều lần
(do dữ liệu trùng lặp nhiều nên nén lại từ to thành rất nhỏ).</p>
<p>Đây là đoạn code ban đầu, nó đọc các dòng text từ 1 file có đuôi <code>.log.gz</code> ra.
Hãy xem kỹ xem bạn có
thể "tối ưu" được bao nhiêu bước và trở thành mấy x từ đây?</p>
<p>Code albv1.py</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">gzip</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">with</span> <span class="n">gzip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">"bigfile.log.gz"</span><span class="p">,</span> <span class="s2">"rt"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="o">.</span><span class="n">readlines</span><span class="p">():</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="mi">10</span><span class="p">:</span>
<span class="k">break</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Proceeded </span><span class="si">{}</span><span class="s2"> lines"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
</code></pre></div>
<p>File nén có đuôi <code>.gz</code> được tạo bởi các chương trình <code>gzip</code>, bên dưới dùng
thư viện <a href="https://www.zlib.net/"><code>zlib</code></a>.
<a href="https://docs.python.org/3/library/gzip.html">Thư viện <code>gzip</code> của Python</a>
cho phép mở file .gz như file text bình thường,
nó thực hiện giải nén phía sau bức màn bí mật. Mode mở file <code>rt</code> giúp lib gzip
hiểu ta muốn thu được <code>str</code> sau khi giải nén, còn khi mặc định nó mở ở mode <code>rb</code>, trả
về kiểu <code>bytes</code>. Chạy đoạn code trên, sử dụng
<code>/usr/bin/time -v</code> để đo thời gian chạy và các thông số chi tiết về bộ nhớ max.
(trên MacOS dùng -l)</p>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time -v python3 albv1.py
Proceeded <span class="m">10</span> lines
...
Maximum resident <span class="nb">set</span> size <span class="o">(</span>kbytes<span class="o">)</span>: <span class="m">693652</span>
...
</code></pre></div>
<p>Code này dùng ~ 600 MB RAM.</p>
<p>Sau khi thay đổi, code chỉ còn dùng ~ 9MB RAM</p>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time -v python3 albv2.py
Proceeded <span class="m">10</span> lines
...
Maximum resident <span class="nb">set</span> size <span class="o">(</span>kbytes<span class="o">)</span>: <span class="m">9752</span>
...
</code></pre></div>
<p>Chỉ thay đổi duy nhất 1 dòng:</p>
<div class="highlight"><pre><span></span><code>$ diff albv1.py albv2.py
12c12
< <span class="k">for</span> line <span class="k">in</span> f.readlines<span class="o">()</span>:
---
> <span class="k">for</span> line <span class="k">in</span> f:
</code></pre></div>
<p>Điều này bất kỳ học viên <a href="https://pymi.vn/">Pymi.vn</a> nào cũng phát hiện ra
ngay, bởi trong hệ thống bài tập đã có 1 bài xử lý file 30 triệu dòng nặng hơn
500 MB tương tự. Cách xử lý từng dòng một:</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
</code></pre></div>
<p>mà không dùng <code>f.readlines()</code> hay <code>f.read()</code>, vì chúng đọc toàn bộ nội dung
file từ ổ cứng vào RAM.
Chú ý cả sự chênh lệch, Python đọc file text 534MB vào thành 670MB RAM.</p>
<p>"Bí kíp" đọc file theo dòng này dù chẳng có gì đặc biệt, <a href="https://docs.python.org/3/tutorial/inputoutput.html#methods-of-file-objects">ghi rõ trong tài liệu
trang chủ</a>
nhưng lại trở thành chuyện lạ với hàng ngàn bài hướng dẫn trên mạng,
thậm chí cả sách cũng dạy dùng readlines.
Hãy thử <a href="https://github.com/search?q=%22readlines%22+language%3APython&type=Code">search
github</a>
có tới hơn 2 triệu kết quả, dù cho github không search chính xác được, thì
chuyện này vẫn không phải là hiếm.</p>
<p>Search <a href="https://stackoverflow.com/questions/3925614/how-do-you-read-a-file-into-a-list-in-python">StackOverFlow python read file into list</a>, trả về hàng loạt kết quả cũng không ổn tí nào.</p>
<p><code>readlines</code> hay <code>read</code> hoàn toàn ok khi lập trình viên làm chủ được kích thước
file đầu vào (file cố định, file được cam kết là nhỏ), nhưng khi file có size
tới hàng
MB, nó sẽ chiếm không ít RAM để chạy chương trình.</p>
<p>Phiên bản <code>v2</code> có thể xử lý file có kích thước lớn tùy ý, 10GB, 100GB, đều vẫn
chỉ dùng < 10MB (với giả thiết kích thước mỗi dòng không quá khác biệt).</p>
<p>Vậy chỉ cần được học Python tử tế, đã trở thành 10x rồi.</p>
<h3>Độ đo</h3>
<p>Để tính mấy x cho rõ ràng, bài này sẽ sử dụng đơn vị đo mà loài người ưa chuộng nhất: tiền.</p>
<p>Tính tiền 1 chương trình trong 1 cái máy thì khá khó, nhưng ngày nay, khi
"cloud computing" là thời thượng, chạy code trên <a href="https://www.familug.org/2017/08/serverless.html">AWS lambda</a>
giúp chuyện tính tiền dễ như học
toán cấp 1. Xem <a href="https://aws.amazon.com/lambda/pricing/">AWS Lambda pricing</a></p>
<p><strong>Tiền</strong> cũng là thước đo lý tưởng khi các công ty ngày nay đua theo
"performance review", "360 review", "data driven"... Một dòng review ghi:
"cắt giảm chi phí 70.000 đô la Biden/năm nhờ tối ưu code" sẽ giúp manager dễ
hiểu, dễ đánh giá hơn hẳn viết "tăng tốc chương trình 100 lần nhờ tối ưu regex
sử dụng non greedy-matching" hay "cải thiện tính đọc được và tính ổn định của
code".</p>
<p>Theo thử nghiệm trên máy, hai phiên bản v1 và v2 chạy về tốc độ là như nhau
(hoặc chênh 1 2 3 giây trên tổng 60s không đáng kể),
thì phần còn lại của biểu thức phụ thuộc vào lượng RAM sử dụng.
Tăng cấu hình RAM cho Lambda function bao nhiêu lần, thì giá gấp bấy nhiêu.
Code đăng ký dùng 256 MB RAM sẽ có giá đắt gấp đôi code đăng ký 128MB.
Với ví dụ trong bài, mỗi file log kích thước cỡ 300-600 MB, lập trình viên sẽ thường
để mức an toàn là 1024MB (1GB) tránh tình trạng có file 800MB xuất hiện mà thiếu RAM.</p>
<p>Code v2 luôn dùng 10MB RAM (=> 100x), nhưng mức tối thiểu AWS Lambda cho phép là
128MB, vậy ở đây tiết kiệm 8 lần => 8x.</p>
<p><strong>Cú twist giật mình</strong>: câu chuyện thực ra không đơn giản vậy, với nhiều RAM hơn,
AWS Lambda sẽ cấp thêm
"năng lượng" cho CPU tỷ lệ với RAM, trong ví dụ này, nếu hầu hết thời gian
chương trình đều để dùng CPU, giảm 8x RAM đăng ký đồng nghĩa với giảm tốc độ CPU 8 lần.
Hay kết quả là v2 chạy mất 8s x 128MB thì v1 chạy mất 1s x 1024 MB, và giá tiền là như nhau.</p>
<blockquote>
<p>Lambda allocates CPU power in proportion to the amount of memory configured.
Memory is the amount of memory available to your Lambda function at runtime.
You can increase or decrease the memory and CPU power allocated to your
function using the Memory (MB) setting. To configure the memory for your
function, set a value between 128 MB and 10,240 MB in 1-MB increments. At
1,769 MB, a function has the equivalent of one vCPU (one vCPU-second of
credits per second).</p>
</blockquote>
<p><a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-memory.html">Lambda configuration memory</a></p>
<p>Theo tài liệu này, nếu function chỉ sử dụng 1 core (code không sử dụng thư viện
multiprocess), tốc độ CPU của nó đạt tối
đa khi cấu hình 1769MB, dù có tăng RAM lên 2048MB thì chỉ tăng thêm 1 core nữa
chứ không làm core ban đầu mạnh lên, hay nói cách khác: không làm code chạy nhanh
hơn.</p>
<p>PS: trong môi trường lượng CPU là cố định (máy ảo, máy vật lý...), v2 vẫn là 100x.</p>
<p><strong>Cú twist số 2</strong>: việc tính tiền trên các hệ thống cloud không hề đơn giản,
có hàng ngàn dịch vụ <a href="https://www.lastweekinaws.com/">cung cấp giải pháp phân tích, đọc hiểu, tối ưu code cloud</a>.
Hay mọc cả ra nghề <a href="https://www.finops.org/introduction/what-is-finops/">FinOps</a>
chuyên về tối ưu hóa <a href="https://aws.amazon.com/blogs/enterprise-strategy/introducing-finops-excuse-me-devsecfinbizops/">cloud cost</a>,
ngành kinh tế trên mây (cloud economics) với các chuyên gia có nghệ danh "cloud economist".</p>
<p><strong>Kết luận 1:</strong> 100x là có thật, nhưng còn phụ thuộc vào hoàn cảnh, không nằm ngoài
"thuyết tương đối".
Việc giảm 8x RAM ở đây không làm giảm chi phí, công việc tiếp theo là cắt giảm
chi phí bằng cách tăng tốc code.</p>
<p>Tuy không cải thiện về đơn vị đo của bài này là <strong>tiền</strong>,
nhưng một đoạn code không
chỉ có mỗi tiền, ngoài hiệu năng, nó còn nhiều tiêu chí khác khó đánh giá hơn
như "tính đọc được" (code dễ đọc), "tính ổn định",...
Dùng <code>for line in f</code> cải thiện được tính ổn định của chương trình, cho phép nó
chạy ngon lành khi kích thước file đầu vào tăng lên - trong khi chương trình
ban đầu sẽ lỗi do không đủ RAM.</p>
<h2>Tăng tốc regex</h2>
<p>Phiên bản sau thêm công đoạn xử lý từng dòng để lọc ra các giá trị mong
muốn, sử dụng công cụ: <a href="https://docs.python.org/3/howto/regex.html">"regular expression"</a> - hay gọi ngắn là <strong>regex</strong>.
Bạn đọc không nên quá tập trung hay sợ hãi khi nhìn vào phần "pattern" viết đống giun dế gì, vì
không nhiều người có khả năng đọc, hiểu, phân tích đoạn này, kể cả có
10 năm đi code hay làm sysadmin. Code regex thường khó đọc khó hiểu,
khi cần dùng chủ yếu các lập trình viên đi copy, đoạn bên dưới cũng là
copy từ link trong comment.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">gzip</span>
<span class="kn">import</span> <span class="nn">regex</span>
<span class="c1"># copied and edited from https://gist.github.com/szinck/d456fbf691483ab77d2453c316db3371</span>
<span class="n">pattern</span> <span class="o">=</span> <span class="s1">'(.*?) (.*?) (.*?) ([0-9.]+):([0-9]*) ([0-9.]+):([0-9]*) ([.0-9]*) ([.0-9]*) ([.0-9]*) (-|[0-9]*) (-|[0-9]*) ([-0-9]*) ([-0-9]*) "(.*?) .*:([0-9]+)([^? ]*)(</span><span class="se">\\</span><span class="s1">x3f?.*?) (.*?)" "(.*?)" (.*?) (.*?) (.*?) "(.*?)" *$'</span>
<span class="c1"># example in AWS docs does not work directly with python https://docs.aws.amazon.com/athena/latest/ug/application-load-balancer-logs.html</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">with</span> <span class="n">gzip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">"bigfile.log.gz"</span><span class="p">,</span> <span class="s2">"rt"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="o">.</span><span class="n">readlines</span><span class="p">():</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">m</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">if</span> <span class="n">m</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">17</span><span class="p">,</span> <span class="mi">11</span><span class="p">))</span>
<span class="c1"># print("sending", result)</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">==</span> <span class="mi">10</span><span class="p">:</span>
<span class="k">break</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Proceeded </span><span class="si">{}</span><span class="s2"> lines"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
</code></pre></div>
<p>Không hiểu gì thì làm sao mà tăng tốc?</p>
<h3>Học regex trong 7 phút</h3>
<p>7 phút đủ để quán bia làm xong món "giò nóng 7 phút".
Trong 7 phút đủ để bạn có kiến thức regex bằng với 90% lập trình viên trên thế
giới.</p>
<h4>regex là gì</h4>
<p>regex là một ngôn ngữ dùng để mô tả pattern (tiếng Việt hay dịch là dạng, mẫu)
của 1 giá trị.
Ví dụ: một số điện thoại di động ở Việt Nam là một số có 10 hay 11 chữ số.
Các pattern sau sẽ "match" (khớp mẫu) số điện thoại:</p>
<ul>
<li><code>\d+</code></li>
<li><code>[0-9]+</code></li>
</ul>
<p>Hai pattern này tương đương, nó sẽ match với 1 hoặc nhiều chữ số viết liền nhau.
Dấu <code>+</code> theo sau là biểu diễn cho "1 hoặc nhiều".
Phổ biến không kém dấu <code>+</code>, là dấu <code>*</code>. Dấu <code>*</code> có nghĩa là match 0 hoặc nhiều:</p>
<ul>
<li><code>\d*</code></li>
<li><code>[0-9]*</code></li>
</ul>
<p>cũng sẽ match số điện thoại di động ở Việt Nam.</p>
<p>Dùng <code>+</code> khẳng định phải có ít nhất 1 số từ 0 đến 9 (ký hiệu <code>[0-9]</code>),
thì dùng <code>*</code> dễ
dãi hơn, không có số nào cũng được. Pattern <code>.*</code> là pattern phổ biến nhất với
người học regex với thời lượng dưới 5 ngày.</p>
<p><code>.</code> đại diện cho 1 ký tự nào đó, nào cũng được, a b c hay 0 1 2 hay % = # gì
cũng ok. <code>.*</code> là pattern match được mọi thứ.</p>
<p>Tránh nhầm lẫn dấu <code>*</code> hay thấy khi gõ câu lệnh Linux, kiểu như <code>ls ~/*.py</code>,
<code>*</code> này không phải regex, đây là khái niệm <code>globbing</code>, mặc dù tác dụng hơi giống,
nó sẽ trả về tất cả các tên file có đuôi <code>.py</code></p>
<p>Dấu <code>?</code> match 0 hoặc 1 lần, pattern <code>https?</code> sẽ match cả <code>http</code> lẫn <code>https</code>.</p>
<p>Dùng trang <a href="https://regex101.com/r/2f1NKt/1">regex101.com</a> để thử các pattern và xem kết quả cho tiện.</p>
<p>Hay bật python3 lên rồi gõ</p>
<div class="highlight"><pre><span></span><code>>>> import re
>>> re.findall<span class="o">(</span><span class="s1">'.*'</span>, <span class="s1">'0987654321'</span><span class="o">)</span>
<span class="o">[</span><span class="s1">'0987654321'</span>, <span class="s1">''</span><span class="o">]</span>
</code></pre></div>
<p>Có thể chỉ định số lần match với cú pháp <code>{ít nhất, nhiều nhất}</code>, pattern
<code>0[0-9]{9, 10}</code> match số điện thoại di động ở Việt Nam.</p>
<p>Đơn giản, đúng không? Hãy viết 1 đoạn regex để kiểm tra 1 email có hợp lệ không.</p>
<p>Mọi người đều biết "chung chung" là email có dạng <code>gìđó@gìđấy.đuôi</code>, vậy
regex có thể là <code>.+@.+\..+</code>. Chú ý dấu <code>.</code> trong tên miền phải gõ <code>\.</code>, vì
dấu <code>.</code> là 1 ký hiệu regex đặc biệt đã nói ở trên.
Đây là <a href="http://www.ex-parrot.com/~pdw/Mail-RFC822-Address.html">regex đầy đủ để kiểm tra 1 địa chỉ email</a>,
và không có một ai trên trái đất này cho rằng nó đơn giản cả.</p>
<p>Đến đây, đã có 1 manh mối để tối ưu tốc độ rồi...
<code>re</code> là standard library
của Python, có sẵn, không cài gì cả. Nhưng đoạn code
trên lại import 1 thư viện tên <a href="https://pypi.org/project/regex/"><code>regex</code></a>.
Đây là thư viện phải cài thêm,</p>
<blockquote>
<p>This regex implementation is backwards-compatible with the standard ‘re’
module, but offers additional functionality.</p>
</blockquote>
<p>Thử thay <code>regex</code> bằng <code>re</code> giúp cắt giảm 10s trong đoạn code chạy 60s trên máy
tác giả.
Có thể "đoán" rằng <code>re</code> có trong stdlib và được tối ưu đủ trò nên chạy nhanh hơn.
Những việc đoán này phải dựa trên cơ sở đo. Để đo tốc độ trong Python,
python có sãn thư viện <code>cProfile</code>.</p>
<h3>Profiling Python với cProfile</h3>
<p>Thay regex bằng re,
chạy lại đoạn code với câu lệnh sau để xem các function nào dùng nhiều thời gian
nhất/gọi nhiều lần nhất.</p>
<ul>
<li><code>tottime</code> total time: tổng thời gian function chạy sau N lần,
không tính chuyện gọi function khác.</li>
<li><code>cumtime</code> cummulative time: tổng thời gian function chạy sau N lần, bao
gồm cả việc gọi các function khác.</li>
</ul>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time -v python3 -m cProfile -s tottime albv1.py
Proceeded <span class="m">50000</span> lines
<span class="m">252074</span> <span class="k">function</span> calls <span class="o">(</span><span class="m">251832</span> primitive calls<span class="o">)</span> <span class="k">in</span> <span class="m">45</span>.948 seconds
Ordered by: internal <span class="nb">time</span>
ncalls tottime percall cumtime percall filename:lineno<span class="o">(</span><span class="k">function</span><span class="o">)</span>
<span class="m">50000</span> <span class="m">45</span>.834 <span class="m">0</span>.001 <span class="m">45</span>.834 <span class="m">0</span>.001 <span class="o">{</span>method <span class="s1">'match'</span> of <span class="s1">'re.Pattern'</span> objects<span class="o">}</span>
<span class="m">1</span> <span class="m">0</span>.033 <span class="m">0</span>.033 <span class="m">45</span>.948 <span class="m">45</span>.948 albv1.py:1<span class="o">(</span><module><span class="o">)</span>
<span class="m">1</span> <span class="m">0</span>.016 <span class="m">0</span>.016 <span class="m">0</span>.061 <span class="m">0</span>.061 <span class="o">{</span>method <span class="s1">'readlines'</span> of <span class="s1">'_io._IOBase'</span> objects<span class="o">}</span>
<span class="m">2277</span> <span class="m">0</span>.014 <span class="m">0</span>.000 <span class="m">0</span>.014 <span class="m">0</span>.000 <span class="o">{</span>built-in method zlib.crc32<span class="o">}</span>
<span class="m">50000</span> <span class="m">0</span>.012 <span class="m">0</span>.000 <span class="m">0</span>.012 <span class="m">0</span>.000 <span class="o">{</span>method <span class="s1">'group'</span> of <span class="s1">'re.Match'</span> objects<span class="o">}</span>
<span class="m">2275</span> <span class="m">0</span>.008 <span class="m">0</span>.000 <span class="m">0</span>.008 <span class="m">0</span>.000 <span class="o">{</span>method <span class="s1">'decompress'</span> of <span class="s1">'zlib.Decompress'</span> objects<span class="o">}</span>
<span class="m">50054</span> <span class="m">0</span>.005 <span class="m">0</span>.000 <span class="m">0</span>.005 <span class="m">0</span>.000 <span class="o">{</span>method <span class="s1">'join'</span> of <span class="s1">'str'</span> objects<span class="o">}</span>
<span class="m">52282</span> <span class="m">0</span>.004 <span class="m">0</span>.000 <span class="m">0</span>.004 <span class="m">0</span>.000 gzip.py:314<span class="o">(</span>closed<span class="o">)</span>
<span class="m">2276</span> <span class="m">0</span>.004 <span class="m">0</span>.000 <span class="m">0</span>.034 <span class="m">0</span>.000 _compression.py:66<span class="o">(</span>readinto<span class="o">)</span>
</code></pre></div>
<p>Kết quả cho thấy hầu hết thời gian đều dùng vào việc chạy regex method <code>match</code>.
<a href="https://github.com/python/cpython/blob/3.10/Modules/_sre.c"><code>re</code> viết bằng C</a>,
lẽ ra phải chạy rất nhanh, thì khi copy thử pattern và 1
ví dụ lên regex101, sẽ thấy lý do vì sao nó chậm. Để kiểm tra pattern có match
string ví dụ sau không, <a href="https://regex101.com/r/hmXaTV/1">re phải dùng tới 112690 bước!!!</a></p>
<blockquote>
<p>http 2015-05-13T23:39:43.945958Z my-loadbalancer 192.168.131.39:2817 10.0.0.1:80 0.000073 0.001048 0.000057 200 200 0 29 "GET http://www. example.com:80/index HTTP/1.1" "curl/7.38.0" - - arn:aws:elasticloadbalancing:us-west-2:123456789012:targetgroup/my-targets/73e2d6bc24d8a067 " Root=1-58337262-36d228ad5d99923122bbe354"</p>
</blockquote>
<p>con số này quá lớn, dù có viết đoạn code lằng nhằng với 20 câu if, cũng
không thể tới 112690 bước.
Tăng tốc regex không phải chuyện dễ, nhưng có 1 tip rất phổ biến: chỗ nào
dùng <code>.*</code> chỗ đó có vẻ chậm/sai/cần tối ưu <a href="https://docs.python.org/3/howto/regex.html#greedy-versus-non-greedy">(khác với <code>.*?</code>)</a>.</p>
<p>Đoạn regex mới</p>
<div class="highlight"><pre><span></span><code><span class="n">pattern</span> <span class="o">=</span> <span class="s1">'(.*?) (.*?) (.*?) ([0-9.]+):([0-9]*) ([0-9.]+):([0-9]*) ([.0-9]*) ([.0-9]*) ([.0-9]*) (-|[0-9]*) (-|[0-9]*) ([-0-9]*) ([-0-9]*) "(.*?) https?://[^:]+:([0-9]+)([^? ]*)(</span><span class="se">\\</span><span class="s1">x3f?.*?) (.*?)" "(.*?)" (.*?) (.*?) (.*?) "(.*?)" *$'</span>
</code></pre></div>
<p>có 1 điểm khác so với đoạn cũ</p>
<div class="highlight"><pre><span></span><code><span class="n">pattern</span> <span class="o">=</span> <span class="s1">'(.*?) (.*?) (.*?) ([0-9.]+):([0-9]*) ([0-9.]+):([0-9]*) ([.0-9]*) ([.0-9]*) ([.0-9]*) (-|[0-9]*) (-|[0-9]*) ([-0-9]*) ([-0-9]*) "(.*?) .*:([0-9]+)([^? ]*)(</span><span class="se">\\</span><span class="s1">x3f?.*?) (.*?)" "(.*?)" (.*?) (.*?) (.*?) "(.*?)" *$'</span>
</code></pre></div>
<p>chỗ <code>.*:([0-9]+)</code> dùng để match domain và port như https://pymi.vn:443, thay <code>.*</code> trong
đoạn này với <code>https?://[^:]+</code> cho tác dụng tương đương. <code>https?://[^:]+</code> match
1 đoạn text bắt đầu bằng http hay https, rồi :// rồi bất cứ thứ gì cho tới khi
gặp dấu : thì dừng lại. Đoạn này <a href="https://regex101.com/r/1HqLtV/1">match sau 524 bước</a></p>
<p>Chú ý đoạn code mới cho rằng mọi url đều bắt đầu với http hay https, điều này
có thể sai (ví dụ wss:// cho websocket), tùy theo yêu cầu bài toán.</p>
<p>Một cách khác, là dùng pattern <code>[^:]+:[^:]+:</code> match mọi thứ có dạng
something:something dừng lại khi gặp dấu : thứ 2.
Pattern sẽ match <code>wss://abcde</code> trong string <code>wss://abcde:443</code>,
pattern này <a href="https://regex101.com/r/pjDFEk/1">match sau 518 bước</a>.</p>
<p>Một cách khác nữa, là dùng <code>.+?:.+?:</code>.
<code>+</code> và <code>*</code> bình thường sẽ match
nhiều nhất có thể, thì khi thêm <code>?</code> sau nó, <code>+?</code> hay <code>*?</code> sẽ match ít nhất có thể.
Đây gọi là tính năng <a href="https://docs.python.org/3/howto/regex.html#greedy-versus-non-greedy">non-greedy</a>
Cho string <code>abcdabcd</code>, <code>a.+d</code> sẽ match <code>abcdabcd</code> nhưng <code>a.+?d</code> chỉ
match <code>abcd</code>. Pattern này <a href="https://regex101.com/r/qSEnKp/1">match sau 543 bước</a>.</p>
<p>Dấu đóng mở ngoặc <code>()</code> dùng để capture kết quả,
kết quả match thành công sẽ được lưu
lại thành 1 group, đánh theo thứ tự xuất hiện.
Đoạn code trong bài lấy ra group 17 và 11
từ kết quả match.</p>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time -v python3 -m cProfile -s tottime albv1_regex.py
Proceeded <span class="m">50000</span> lines
<span class="m">252201</span> <span class="k">function</span> calls <span class="o">(</span><span class="m">251956</span> primitive calls<span class="o">)</span> <span class="k">in</span> <span class="m">0</span>.310 seconds
Ordered by: internal <span class="nb">time</span>
ncalls tottime percall cumtime percall filename:lineno<span class="o">(</span><span class="k">function</span><span class="o">)</span>
<span class="m">50000</span> <span class="m">0</span>.208 <span class="m">0</span>.000 <span class="m">0</span>.208 <span class="m">0</span>.000 <span class="o">{</span>method <span class="s1">'match'</span> of <span class="s1">'re.Pattern'</span> objects<span class="o">}</span>
<span class="m">1</span> <span class="m">0</span>.026 <span class="m">0</span>.026 <span class="m">0</span>.310 <span class="m">0</span>.310 albv1_regex.py:1<span class="o">(</span><module><span class="o">)</span>
<span class="m">1</span> <span class="m">0</span>.016 <span class="m">0</span>.016 <span class="m">0</span>.061 <span class="m">0</span>.061 <span class="o">{</span>method <span class="s1">'readlines'</span> of <span class="s1">'_io._IOBase'</span> objects<span class="o">}</span>
<span class="m">2277</span> <span class="m">0</span>.014 <span class="m">0</span>.000 <span class="m">0</span>.014 <span class="m">0</span>.000 <span class="o">{</span>built-in method zlib.crc32<span class="o">}</span>
<span class="m">50000</span> <span class="m">0</span>.008 <span class="m">0</span>.000 <span class="m">0</span>.008 <span class="m">0</span>.000 <span class="o">{</span>method <span class="s1">'group'</span> of <span class="s1">'re.Match'</span> objects<span class="o">}</span>
<span class="m">2275</span> <span class="m">0</span>.008 <span class="m">0</span>.000 <span class="m">0</span>.008 <span class="m">0</span>.000 <span class="o">{</span>method <span class="s1">'decompress'</span> of <span class="s1">'zlib.Decompress'</span> objects<span class="o">}</span>
<span class="m">52282</span> <span class="m">0</span>.004 <span class="m">0</span>.000 <span class="m">0</span>.004 <span class="m">0</span>.000 gzip.py:314<span class="o">(</span>closed<span class="o">)</span>
<span class="m">50054</span> <span class="m">0</span>.004 <span class="m">0</span>.000 <span class="m">0</span>.004 <span class="m">0</span>.000 <span class="o">{</span>method <span class="s1">'join'</span> of <span class="s1">'str'</span> objects<span class="o">}</span>
<span class="m">2276</span> <span class="m">0</span>.004 <span class="m">0</span>.000 <span class="m">0</span>.030 <span class="m">0</span>.000 gzip.py:454<span class="o">(</span><span class="nb">read</span><span class="o">)</span>
</code></pre></div>
<p>45 giây -> xuống còn 0.3 giây cho ta cảm giác code mới chắc là sai nên mới 150x như vậy.</p>
<h4>Sửa regex xong làm sao biết đúng sai?</h4>
<p>Một cách đơn giản là thử, cho 2 pattern match và lấy kết quả ra rồi so sánh lần
lượt với mỗi dòng log, qua cả file đều giống nhau là có vẻ ổn rồi.</p>
<div class="highlight"><pre><span></span><code><span class="n">p1</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern1</span><span class="p">)</span>
<span class="n">p2</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern2</span><span class="p">)</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
<span class="n">m1</span> <span class="o">=</span> <span class="n">p1</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="n">m2</span> <span class="o">=</span> <span class="n">p2</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">if</span> <span class="n">m1</span><span class="p">:</span>
<span class="k">assert</span> <span class="n">m1</span><span class="o">.</span><span class="n">groups</span><span class="p">()</span> <span class="o">==</span> <span class="n">m2</span><span class="o">.</span><span class="n">group</span><span class="p">(),</span> <span class="p">(</span><span class="n">m1</span><span class="p">,</span> <span class="n">m2</span><span class="p">,</span> <span class="n">line</span><span class="p">)</span>
</code></pre></div>
<p>PS: khi tối ưu đoạn code này, vô tình phát hiện ra đoạn code ban đầu xử lý
không đúng khi URL path có chứa dấu <code>:</code>, 3 phiên bản mới không gặp phải vấn
đề này.</p>
<p><strong>Kết luận 2:</strong> 150x là có thật và không cần phải học "regex" nâng cao.</p>
<h4>Học và dùng regex</h4>
<p>Python có viết 1 tài liệu cơ bản về regex trong mục <a href="https://docs.python.org/3/howto/regex.html">howto</a>,
nếu một ngày buồn chán quá không có gì làm, hay muốn tìm ý nghĩa của cuộc sống, bạn có thể ngồi học regex.</p>
<p><img alt="xkcd" src="https://imgs.xkcd.com/comics/regular_expressions.png"></p>
<p>Hoặc đi tìm một "khóa học regex"? cái này chưa có, cơ hội khởi nghiệp làm giàu còn rất rộng mở
cho các chuyên gia công nghệ bán "khoá học làm chủ regex để học sâu với trí tuệ nhân tạo 4.0".</p>
<p>Regex là một công cụ mạnh, nhưng khó dùng đúng, nó giải quyết được 1 số bài toán,
<a href="https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags?noredirect=1&lq=1">1 số thì không và nên dùng cách khác đơn giản hơn như parse HTML</a>.</p>
<p>regex <a href="https://blog.codinghorror.com/regular-expressions-now-you-have-two-problems/">nổi tiếng phức tạp</a>, và từng tạo ra không ít <a href="https://stackstatus.net/post/147710624694/outage-postmortem-july-20-2016">sự cố trong các hệ thống lớn toàn cầu</a>.</p>
<blockquote>
<p>Some people, when confronted with a problem, think "I know, I'll use regular expressions." Now they have two problems.</p>
</blockquote>
<p>Khi một bài toán có thể giải quyết theo 1 cách khác đơn giản hơn, thì nên tránh
dùng regex.</p>
<p>Chú ý: trong ví dụ này, nhằm mục tiêu giữ lại code gần giống với code
ban đầu nhất, không thay đổi quá nhiều, nên đã không viết lại đoạn code
lọc ra urlpath và statuscode mà vẫn dùng regex.</p>
<h2>Tăng tốc gửi message đến kafka</h2>
<h3>Kafka là gì</h3>
<blockquote>
<p>Apache Kafka is an open-source distributed event streaming platform used by thousands of companies for high-performance data pipelines, streaming analytics, data integration, and mission-critical applications.</p>
</blockquote>
<p><a href="https://kafka.apache.org/">https://kafka.apache.org/</a></p>
<p>Nói đơn giản, Kafka như hệ thống ống nước, mạng lưới điện, mạng lưới viễn thông,
nó đủ tính năng để đáp ứng mọi nhu cầu truyền tải dữ liệu từ nhiều nguồn đến
nhiều đích. Kafka được dùng phổ biến trong các doanh nghiệp, các hệ thống xử
lý data, logging...</p>
<p>Kafka có nhiều tính năng, bài này sử dụng nó như 1 hệ thống pub-sub, tức có 1
bên gửi message đi, và 1/nhiều bên nhận message.</p>
<h3>Cài đặt kafka</h3>
<p>Cài Java</p>
<div class="highlight"><pre><span></span><code>sudo apt-get update <span class="o">&&</span> sudo apt-get install -y openjdk-11-jre-headless
</code></pre></div>
<p>Cài đặt kafka đơn giản với <a href="https://kafka.apache.org/quickstart">5 bước không cần sudo</a></p>
<div class="highlight"><pre><span></span><code>curl -LO https://mirror.downloadvn.com/apache/kafka/2.8.0/kafka_2.13-2.8.0.tgz
tar xvf kafka_2.13-2.8.0.tgz
<span class="nb">cd</span> kafka_2.13-2.8.0
bin/zookeeper-server-start.sh config/zookeeper.properties
<span class="c1">## mở 1 terminal khác</span>
<span class="nb">cd</span> kafka_2.13-2.8.0
bin/kafka-server-start.sh config/server.properties
</code></pre></div>
<h4>Python Kafka producer</h4>
<p>Phiên bản đầy đủ của chương trình trước khi mang đi tối ưu: nó
đọc file log <a href="https://aws.amazon.com/premiumsupport/knowledge-center/athena-analyze-access-logs/">AWS ALB</a>
đã nén <code>.gz</code>, lấy ra urlpath và status code bằng regex, rồi gửi
kết quả đến kafka.</p>
<div class="highlight"><pre><span></span><code>pip install kafka-python
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">gzip</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">from</span> <span class="nn">kafka</span> <span class="kn">import</span> <span class="n">KafkaProducer</span>
<span class="n">producer</span> <span class="o">=</span> <span class="n">KafkaProducer</span><span class="p">(</span><span class="n">linger_ms</span><span class="o">=</span><span class="mi">5</span><span class="p">,</span> <span class="n">batch_size</span><span class="o">=</span><span class="mi">65536</span><span class="p">,</span> <span class="n">acks</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">compression_type</span><span class="o">=</span><span class="s2">"lz4"</span><span class="p">)</span>
<span class="n">pattern</span> <span class="o">=</span> <span class="s1">'(.*?) (.*?) (.*?) ([0-9.]+):([0-9]*) ([0-9.]+):([0-9]*) ([.0-9]*) ([.0-9]*) ([.0-9]*) (-|[0-9]*) (-|[0-9]*) ([-0-9]*) ([-0-9]*) "(.*?) [^:]+:[^:]+:([0-9]+)([^? ]*)(</span><span class="se">\\</span><span class="s1">x3f?.*?) (.*?)" "(.*?)" (.*?) (.*?) (.*?) "(.*?)" *$'</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">with</span> <span class="n">gzip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">"bigfile.log.gz"</span><span class="p">,</span> <span class="s2">"rt"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">10000</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
<span class="n">m</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">if</span> <span class="n">m</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">17</span><span class="p">,</span> <span class="mi">11</span><span class="p">))</span>
<span class="n">producer</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s2">"alblog"</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Processed </span><span class="si">{}</span><span class="s2"> lines"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
</code></pre></div>
<p>Khi chưa gửi message tới kafka, code chạy mất 8s, khi gửi đến kafka trên cùng
máy, code chạy mất 60s (khi profile chậm hơn nữa do quá trình profile can thiệp ảnh hưởng tới tốc độ):</p>
<div class="highlight"><pre><span></span><code><span class="k">Proc</span><span class="nb">es</span><span class="nv">sed</span> <span class="mi">1500000</span> <span class="nv">lines</span>
<span class="err">133875187</span> <span class="nf">function</span> <span class="nv">calls</span> <span class="p">(</span><span class="mi">133873425</span> <span class="nv">primitive</span> <span class="nv">calls</span><span class="p">)</span> <span class="nv">in</span> <span class="mf">91.895</span> <span class="nv">seconds</span>
<span class="nf">Ordered</span> <span class="nv">by</span><span class="p">:</span> <span class="nv">internal</span> <span class="nv">time</span>
<span class="no">ncalls</span><span class="kd"> tottime</span> <span class="nv">percall</span> <span class="nv">cumtime</span> <span class="nv">percall</span> <span class="nv">filename</span><span class="p">:</span><span class="nv">lineno</span><span class="p">(</span><span class="nv">function</span><span class="p">)</span>
<span class="err">1500001</span> <span class="err">8</span><span class="nf">.978</span> <span class="mf">0.000</span> <span class="mf">8.978</span> <span class="mf">0.000</span> <span class="err">{</span><span class="nv">method</span> <span class="s">'match'</span> <span class="nv">of</span> <span class="s">'re.Pattern'</span> <span class="nv">objects</span><span class="err">}</span>
<span class="err">1500000</span> <span class="err">8</span><span class="nf">.602</span> <span class="mf">0.000</span> <span class="mf">73.735</span> <span class="mf">0.000</span> <span class="nv">kafka.py</span><span class="p">:</span><span class="mi">538</span><span class="p">(</span><span class="nv">send</span><span class="p">)</span>
<span class="err">1500282</span> <span class="err">7</span><span class="nf">.845</span> <span class="mf">0.000</span> <span class="mf">14.095</span> <span class="mf">0.000</span> <span class="nv">default_records.py</span><span class="p">:</span><span class="mi">406</span><span class="p">(</span><span class="nv">append</span><span class="p">)</span>
<span class="err">1500000</span> <span class="err">6</span><span class="nf">.948</span> <span class="mf">0.000</span> <span class="mf">39.138</span> <span class="mf">0.000</span> <span class="nv">record_accumulator.py</span><span class="p">:</span><span class="mi">200</span><span class="p">(</span><span class="nv">append</span><span class="p">)</span>
<span class="err">1500282</span> <span class="err">4</span><span class="nf">.675</span> <span class="mf">0.000</span> <span class="mf">28.579</span> <span class="mf">0.000</span> <span class="nv">record_accumulator.py</span><span class="p">:</span><span class="mi">57</span><span class="p">(</span><span class="nv">try_append</span><span class="p">)</span>
<span class="err">1</span> <span class="err">4</span><span class="nf">.299</span> <span class="mf">4.299</span> <span class="mf">92.439</span> <span class="mf">92.439</span> <span class="nb">al</span><span class="nv">bv1_regex2.py</span><span class="p">:</span><span class="mi">1</span><span class="p">(</span><span class="o"><</span><span class="nv">module</span><span class="o">></span><span class="p">)</span>
<span class="err">1500000</span> <span class="err">4</span><span class="nf">.147</span> <span class="mf">0.000</span> <span class="mf">6.439</span> <span class="mf">0.000</span> <span class="nv">future.py</span><span class="p">:</span><span class="mi">32</span><span class="p">(</span><span class="nv">__init__</span><span class="p">)</span>
<span class="err">7501128</span> <span class="err">3</span><span class="nf">.138</span> <span class="mf">0.000</span> <span class="mf">4.001</span> <span class="mf">0.000</span> <span class="nv">util.py</span><span class="p">:</span><span class="mi">10</span><span class="p">(</span><span class="nv">encode_varint</span><span class="p">)</span>
<span class="err">1500000</span> <span class="err">2</span><span class="nf">.754</span> <span class="mf">0.000</span> <span class="mf">12.242</span> <span class="mf">0.000</span> <span class="nv">kafka.py</span><span class="p">:</span><span class="mi">716</span><span class="p">(</span><span class="nv">_partition</span><span class="p">)</span>
<span class="err">3000001</span> <span class="err">2</span><span class="nf">.270</span> <span class="mf">0.000</span> <span class="mf">2.688</span> <span class="mf">0.000</span> <span class="nb">cl</span><span class="nv">uster.py</span><span class="p">:</span><span class="mi">106</span><span class="p">(</span><span class="nv">partitions_for_topic</span><span class="p">)</span>
<span class="err">1500000</span> <span class="err">1</span><span class="nf">.946</span> <span class="mf">0.000</span> <span class="mf">4.090</span> <span class="mf">0.000</span> <span class="nb">cl</span><span class="nv">uster.py</span><span class="p">:</span><span class="mi">119</span><span class="p">(</span><span class="nv">available_partitions_for_topic</span><span class="p">)</span>
<span class="err">1500000</span> <span class="err">1</span><span class="nf">.840</span> <span class="mf">0.000</span> <span class="mf">4.318</span> <span class="mf">0.000</span> <span class="nv">kafka.py</span><span class="p">:</span><span class="mi">664</span><span class="p">(</span><span class="nv">_wait_on_metadata</span><span class="p">)</span>
<span class="err">65554</span> <span class="err">1</span><span class="nf">.721</span> <span class="mf">0.000</span> <span class="mf">1.721</span> <span class="mf">0.000</span> <span class="err">{</span><span class="nv">built</span><span class="o">-</span><span class="nv">in</span> <span class="nv">method</span> <span class="nv">zlib.crc32</span><span class="err">}</span>
<span class="err">1500000</span> <span class="err">1</span><span class="nf">.682</span> <span class="mf">0.000</span> <span class="mf">2.811</span> <span class="mf">0.000</span> <span class="nv">default_records.py</span><span class="p">:</span><span class="mi">563</span><span class="p">(</span><span class="nb">si</span><span class="nv">ze_of</span><span class="p">)</span>
<span class="err">15397286/15397055</span> <span class="err">1</span><span class="nf">.615</span> <span class="mf">0.000</span> <span class="mf">1.615</span> <span class="mf">0.000</span> <span class="err">{</span><span class="nv">built</span><span class="o">-</span><span class="nv">in</span> <span class="nv">method</span> <span class="nv">builtins.len</span><span class="err">}</span>
<span class="nf">...</span>
</code></pre></div>
<p>Việc gửi 1.5 triệu message tới kafka chạy cùng máy mất tới 73s (cumtime)
trong output profiling. Trong đó có dòng thứ 3, 4 và 5 đều là "append".
Mở code của thư viện <a href="https://github.com/dpkp/kafka-python/">kafka-python</a>
ra xem, phần <a href="https://github.com/dpkp/kafka-python/blob/2.0.2/kafka/producer/record_accumulator.py#L200">này</a>
append các message vào
1 list "accumulator", để bao giờ đủ 65536 phần tử mới gửi đi
(batch_size=65536 lúc tạo producer). Batching là tính năng phổ biến trong các
thư viện liên quan tới network, do việc gửi nhận qua network thường chậm, nên
gom lại một đống rồi gửi đi để giảm số lần gửi/nhận. Nhưng trong ví dụ này,
việc batching tốn tới 20s thì có vẻ không ổn.
Sau một hồi chỉnh sửa các tham số (batch_size, linger_ms ...), giải pháp lại là
"think outside of the box", tìm xem Python còn có thư viện kafka nào khác không.</p>
<p>Thư viện <a href="https://github.com/confluentinc/confluent-kafka-python">confluent-kafka-python</a> sử dụng
<a href="https://github.com/edenhill/librdkafka">librdkafka</a> viết bằng C hứa hẹn cho một performance
tốt hơn nhiều</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">gzip</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">from</span> <span class="nn">confluent_kafka</span> <span class="kn">import</span> <span class="n">Producer</span>
<span class="n">producer</span> <span class="o">=</span> <span class="n">Producer</span><span class="p">(</span>
<span class="p">{</span><span class="s1">'bootstrap.servers'</span><span class="p">:</span> <span class="s2">"localhost:9092"</span><span class="p">,</span>
<span class="s1">'queue.buffering.max.messages'</span><span class="p">:</span> <span class="mi">65536</span><span class="p">,</span>
<span class="s1">'acks'</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span>
<span class="s1">'compression.type'</span><span class="p">:</span> <span class="s1">'lz4'</span><span class="p">}</span>
<span class="p">)</span>
<span class="n">pattern</span> <span class="o">=</span> <span class="s1">'(.*?) (.*?) (.*?) ([0-9.]+):([0-9]*) ([0-9.]+):([0-9]*) ([.0-9]*) ([.0-9]*) ([.0-9]*) (-|[0-9]*) (-|[0-9]*) ([-0-9]*) ([-0-9]*) "(.*?) [^:]+:[^:]+:([0-9]+)([^? ]*)(</span><span class="se">\\</span><span class="s1">x3f?.*?) (.*?)" "(.*?)" (.*?) (.*?) (.*?) "(.*?)" *$'</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="n">pattern</span><span class="p">)</span>
<span class="n">i</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">with</span> <span class="n">gzip</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="s2">"bigfile.log.gz"</span><span class="p">,</span> <span class="s2">"rt"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
<span class="n">i</span> <span class="o">=</span> <span class="n">i</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">10000</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
<span class="n">m</span> <span class="o">=</span> <span class="n">p</span><span class="o">.</span><span class="n">match</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">if</span> <span class="n">m</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="s2">" "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">m</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="mi">17</span><span class="p">,</span> <span class="mi">11</span><span class="p">))</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">producer</span><span class="o">.</span><span class="n">produce</span><span class="p">(</span><span class="s2">"alblog"</span><span class="p">,</span> <span class="n">result</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">))</span>
<span class="n">producer</span><span class="o">.</span><span class="n">poll</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">except</span> <span class="ne">BufferError</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Buffer full, waiting for free space on the queue"</span><span class="p">)</span>
<span class="n">producer</span><span class="o">.</span><span class="n">poll</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="n">producer</span><span class="o">.</span><span class="n">flush</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Processed </span><span class="si">{}</span><span class="s2"> lines"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
</code></pre></div>
<p>và thực nghiệm thấy đúng là như thế:</p>
<div class="highlight"><pre><span></span><code><span class="n">Processed</span> <span class="mi">1500000</span> <span class="n">lines</span>
<span class="mi">11882360</span> <span class="n">function</span> <span class="n">calls</span> <span class="p">(</span><span class="mi">11882085</span> <span class="n">primitive</span> <span class="n">calls</span><span class="p">)</span> <span class="ow">in</span> <span class="mf">21.700</span> <span class="n">seconds</span>
<span class="n">Ordered</span> <span class="n">by</span><span class="p">:</span> <span class="n">internal</span> <span class="n">time</span>
<span class="n">ncalls</span> <span class="n">tottime</span> <span class="n">percall</span> <span class="n">cumtime</span> <span class="n">percall</span> <span class="n">filename</span><span class="p">:</span><span class="n">lineno</span><span class="p">(</span><span class="n">function</span><span class="p">)</span>
<span class="mi">1500000</span> <span class="mf">9.328</span> <span class="mf">0.000</span> <span class="mf">9.328</span> <span class="mf">0.000</span> <span class="p">{</span><span class="n">method</span> <span class="s1">'match'</span> <span class="n">of</span> <span class="s1">'re.Pattern'</span> <span class="n">objects</span><span class="p">}</span>
<span class="mi">1500000</span> <span class="mf">4.197</span> <span class="mf">0.000</span> <span class="mf">4.197</span> <span class="mf">0.000</span> <span class="p">{</span><span class="n">method</span> <span class="s1">'produce'</span> <span class="n">of</span> <span class="s1">'cimpl.Producer'</span> <span class="n">objects</span><span class="p">}</span>
<span class="mi">1</span> <span class="mf">3.457</span> <span class="mf">3.457</span> <span class="mf">21.700</span> <span class="mf">21.700</span> <span class="n">albv1_regex2</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">1</span><span class="p">(</span><span class="o"><</span><span class="n">module</span><span class="o">></span><span class="p">)</span>
<span class="mi">1500000</span> <span class="mf">1.511</span> <span class="mf">0.000</span> <span class="mf">1.511</span> <span class="mf">0.000</span> <span class="p">{</span><span class="n">method</span> <span class="s1">'poll'</span> <span class="n">of</span> <span class="s1">'cimpl.Producer'</span> <span class="n">objects</span><span class="p">}</span>
<span class="mi">65554</span> <span class="mf">0.519</span> <span class="mf">0.000</span> <span class="mf">0.519</span> <span class="mf">0.000</span> <span class="p">{</span><span class="n">built</span><span class="o">-</span><span class="ow">in</span> <span class="n">method</span> <span class="n">zlib</span><span class="o">.</span><span class="n">crc32</span><span class="p">}</span>
<span class="mi">1500000</span> <span class="mf">0.410</span> <span class="mf">0.000</span> <span class="mf">0.410</span> <span class="mf">0.000</span> <span class="p">{</span><span class="n">method</span> <span class="s1">'group'</span> <span class="n">of</span> <span class="s1">'re.Match'</span> <span class="n">objects</span><span class="p">}</span>
<span class="mi">65552</span> <span class="mf">0.386</span> <span class="mf">0.000</span> <span class="mf">0.386</span> <span class="mf">0.000</span> <span class="p">{</span><span class="n">method</span> <span class="s1">'decompress'</span> <span class="n">of</span> <span class="s1">'zlib.Decompress'</span> <span class="n">objects</span><span class="p">}</span>
<span class="mi">1565559</span> <span class="mf">0.255</span> <span class="mf">0.000</span> <span class="mf">0.255</span> <span class="mf">0.000</span> <span class="n">gzip</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">314</span><span class="p">(</span><span class="n">closed</span><span class="p">)</span>
<span class="mi">1500117</span> <span class="mf">0.232</span> <span class="mf">0.000</span> <span class="mf">0.232</span> <span class="mf">0.000</span> <span class="p">{</span><span class="n">method</span> <span class="s1">'join'</span> <span class="n">of</span> <span class="s1">'str'</span> <span class="n">objects</span><span class="p">}</span>
<span class="mi">65553</span> <span class="mf">0.218</span> <span class="mf">0.000</span> <span class="mf">1.687</span> <span class="mf">0.000</span> <span class="n">_compression</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">66</span><span class="p">(</span><span class="n">readinto</span><span class="p">)</span>
<span class="mi">65553</span> <span class="mf">0.207</span> <span class="mf">0.000</span> <span class="mf">1.417</span> <span class="mf">0.000</span> <span class="n">gzip</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">454</span><span class="p">(</span><span class="n">read</span><span class="p">)</span>
</code></pre></div>
<p>Từ 91s -> 21.7s, ta rút gọn thêm được 4x.</p>
<p><strong>Kết luận 3:</strong> thư viện có this có that, hãy tự đo và kiểm tra các options.</p>
<h2>Kết quả</h2>
<p>Sau 3 lần tối ưu, code mới dùng 100x ít RAM hơn, nhanh gấp 150 * 4 == 600 lần
code ban đầu, chi phí giảm: 600 lần so với ban đầu. Giả sử đoạn code này hiện
đang tốn $6000/tháng (6 * 12 = $72k/năm), thì sau khi tối ưu còn $10/tháng -> $120/năm.</p>
<p>$70.000 mỗi năm được tiết kiệm này nên dùng để thưởng nóng cho 10x dev hay
chỉ thưởng $7k?</p>
<p>Thực tế (kinh tế) thì không phải thế, thưởng nóng $1-2-3k đã là nhiều lắm rồi.
Không bàn tới chuyện quản lý doanh nghiệp/nhân sự/kinh tế ở đây, nhưng có thể
thấy 1 điều, 1 lập trình viên có mức lương cao ngất ngưởng không phải là
chuyện vô lý, khi họ có thể tự trả lương cho mình 1 vài năm trong vòng 1 2 ngày
tối ưu code.</p>
<h2>Kết luận</h2>
<p>10x engineering là có thật. 10x phụ thuộc vào
hoàn cảnh, tiêu chí đánh giá, nhưng học Python tại PyMi.vn rõ ràng là khoản đầu tư 10x.
Việc tối ưu code, sử dụng cProfile của Python là một công việc thú vị, nhưng
cần đặt vào đúng chỗ. Tối ưu đoạn code tiêu tốn $72k/năm để còn $120/năm
mang lại lợi ích kinh tế khác biệt (trong môi trường doanh nghiệp) so với tối ưu đoạn code $72/năm còn $0.12/năm.</p>
<p>Bạn đọc đam mê có thể tiếp tục tối ưu và gửi kết quả tới tác giả bằng cách
tạo <a href="https://github.com/pymivn/people/pulls">1 pull request trên GitHub</a>.
Ngoài ra, có thể viết hẳn 1 chương trình hẳn hoi giúp lấy log AWS load balancer
về rồi gửi tới 1 đích đến tùy ý. Đây là <a href="https://www.google.com/search?q=parse+aws+application+load+balancer+logs&hl=en">một vấn đề phổ biến khi dùng AWS Load Balancer
mà chưa có giải pháp triệt để.</a></p>
<h2>Không phải kết luận</h2>
<p>Giống như mọi bài viết về optimize/benchmark, kết quả rất phụ thuộc và bài toán
cụ thể, môi trường (phiên bản, hệ điều hành ...) cụ thể.
Bài viết này <strong>KHÔNG</strong> kết luận:</p>
<ul>
<li><code>for line in f</code> nhanh hay chậm hơn <code>for line in f.readlines()</code></li>
<li>Lib <code>re</code> nhanh hơn lib <code>regex</code></li>
<li>confluent-kafka-python nhanh hơn kafka-python</li>
</ul>
<p>Kết quả có thể hoàn toàn bị đảo ngược khi test trên 1 hệ điều hành khác (như
MacOS, Windows) hay một phiên bản Python/thư viện khác, hay từng đoạn code
chạy riêng sẽ khác với khi 2,3 đoạn code kết hợp lại.
Kết luận chỉ nên đưa ra khi đem đi chạy thật với tình huống cụ thể, đến lúc
tính tiền.</p>
<h2>References</h2>
<ul>
<li><a href="https://www.familug.org/2017/08/serverless.html">serverless</a></li>
<li><a href="https://www.familug.org/2014/09/shell-globbing-not-regex.html">globbing vs regex</a></li>
<li><a href="https://www.familug.org/2020/10/grep-khong-ho-tro-d-va-3-mode-regex.html">grep & 3 modes of regex</a></li>
</ul>
<h2>Hết</h2>
<p>Bài viết thực hiện trên</p>
<div class="highlight"><pre><span></span><code>$ grep <span class="nv">VERSION</span><span class="o">=</span> /etc/os-release<span class="p">;</span> python3 --version
<span class="nv">VERSION</span><span class="o">=</span><span class="s2">"20.04.2 LTS (Focal Fossa)"</span>
Python <span class="m">3</span>.8.5
</code></pre></div>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>.</p>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
</ul>Có Salt không lo chết đói2021-06-19T00:00:00+07:002021-06-19T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2021-06-19:/article/salt/<p>Tự động hóa cài đặt, cấu hình (deploy) Python app với Salt</p><p><img alt="salt" src="https://gitlab.com/saltstack/open/salt-branding-guide/-/raw/master/logos/SaltProject_altlogo_teal.png?inline=true"></p>
<h2>Salt là gì</h2>
<p>Salt (/sɔːlt/ /sɒlt/) là một phần mềm mã nguồn mở, một hệ thống
thuộc nhóm Configuration Management (CM), viết bằng Python, sử dụng YAML làm ngôn
ngữ giao tiếp với người dùng.</p>
<h2>Salt làm gì?</h2>
<p>Tính năng của Salt chia làm 2 phần chính: remote execution và configuration
management dựa trên nền tảng remote execution.</p>
<h3>Remote execution</h3>
<p>chạy lệnh từ xa. Sau khi cài đặt xong salt-master và các salt-minion, từ
salt-master có thể chạy bất kì câu lệnh nào trên máy cài salt-minion.</p>
<p>Tưởng tượng bạn cần xoá 1 file trên 10 hay 100 máy, chỉ cần gõ 1 câu lệnh và
tất cả các minion sẽ chạy câu lệnh ấy. Sức mạnh là vô cùng khủng khiếp, giống
như nắm một mạng botnet trong tay vậy. Muốn ping 1 server từ 100 máy? gõ lệnh
<code>ping -c N victim</code>, và 100 máy sẽ cùng lúc ping đến máy đích.</p>
<h3>Configuration management</h3>
<p>đảm bảo trạng thái các thành phần của hệ thống. Cần đảm bảo 1 service NGINX
chạy với file cấu hình nhất định, forward request đến gunicorn app
server trên máy ấy, đã cấu hình để chạy một web application viết bằng Python
với phiên bản mới nhất lấy từ 1 git repository? Salt làm được tất cả điều đó,
và nó có thể đảm bảo rằng mọi thay đổi bằng tay thực hiện trên những thành
phần nói trên sẽ bị thay thế bằng những gì đã định trước.</p>
<p>Theo <a href="https://www.familug.org/2015/06/saltstack-chao-muoi-em-la-ai.html">Chào Muối, em là ai? </a></p>
<h2>Mục tiêu</h2>
<p>Bài này cài đặt salt qua <code>pip</code>, và dùng <code>salt-ssh</code> để điều khiển máy khác.
Mục tiêu đưa ra một bài hướng dẫn thực hành đơn giản hơn bài hướng dẫn trên
trang chủ <a href="https://docs.saltproject.io/en/master/topics/tutorials/walkthrough.html#salt-in-10-minutes/">Salt in 10 minutes</a>
và mang lại cảm giác giống đang dùng <a href="https://www.familug.org/2016/12/ansible-command.html">Ansible</a>.</p>
<p>Kết quả: deploy uds bot - 1 chương trình Python lên máy Ubuntu 18.04, cấu hình
systemd chạy vù vù.</p>
<h2>3 phút dành cho lý thuyết</h2>
<ul>
<li>Máy ra lệnh gọi là salt master</li>
<li>Máy bị điều khiển, chạy các câu lệnh gọi là salt minion, trong bài này có IP <code>192.168.0.110</code></li>
<li>Trong bài này, master sẽ truy cập vào minion qua ssh.</li>
<li>Máy master không cần quyền gì đặc biệt và đã cài đặt salt trên Python3.6+</li>
<li>Máy minion chạy <code>*NIX</code> OS (Ubuntu, Debian, Fedora, .. MacOS),
phải có quyền sudo không password hoặc root để toàn quyền điều khiển máy (như cài package qua
apt). <a href="https://docs.saltproject.io/en/latest/topics/installation/windows.html">Salt có hỗ trợ Windows</a> nhưng nằm ngoài phạm vi bài viết này.</li>
</ul>
<h2>Trên máy minion</h2>
<ul>
<li>Tạo 1 user mới: <code>sudo adduser saltuser</code>, trả lời các câu hỏi và gõ password</li>
<li>sudo không password: thêm <code>saltuser ALL=(ALL) NOPASSWD: ALL</code> vào cuối file /etc/sudoers</li>
<li>Có chương trình sshd listen trên port 22, cài bằng lệnh <code>sudo apt install openssh-server</code>, gõ <code>ss -nlt | grep 22</code> thấy có kết quả là ok.</li>
</ul>
<h2>Trên máy master</h2>
<p>Tạo SSH key nếu chưa có:</p>
<div class="highlight"><pre><span></span><code>ls ~/.ssh/id_rsa <span class="o">||</span> ssh-keygen
</code></pre></div>
<p>Copy ssh public key vào minion:</p>
<div class="highlight"><pre><span></span><code>ssh-copy-id saltuser@192.168.0.110
<span class="c1"># rồi gõ password saltuser vừa tạo</span>
</code></pre></div>
<h3>Cài đặt</h3>
<p>Tạo 1 virtualenv:</p>
<div class="highlight"><pre><span></span><code>python3 -m venv saltenv
. saltenv/bin/activate
pip install <span class="nv">salt</span><span class="o">==</span><span class="m">3002</span> <span class="c1"># do bản 3003 mới nhất đang có bug</span>
</code></pre></div>
<p>Cài xong kiểm tra</p>
<div class="highlight"><pre><span></span><code>$ salt-ssh --version
salt-ssh <span class="m">3002</span>
</code></pre></div>
<h3>Cấu hình master</h3>
<p>Có 2 file cần tạo: <code>master</code> và <code>roster</code></p>
<p>Tạo 1 thư mục tên saltlab:</p>
<div class="highlight"><pre><span></span><code>mkdir -p ~/saltlab/states
<span class="nb">cd</span> ~/saltlab
<span class="nb">pwd</span>
</code></pre></div>
<p>File <code>master</code> chứa 2 dòng, <code>root_dir</code> giá trị là đường dẫn đầy đủ tới
thư muc saltlab, <code>file_roots</code> có <code>states</code> với giá trị là đường dẫn đầy đủ
tới thư mục <code>states</code>, thư mục states sẽ chứa các file "saltstate"</p>
<div class="highlight"><pre><span></span><code><span class="nt">root_dir</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">/home/hvn/saltlab</span>
<span class="nt">file_roots</span><span class="p">:</span>
<span class="nt">base</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">/home/hvn/saltlab/states</span>
<span class="nt">pillar_roots</span><span class="p">:</span>
<span class="nt">base</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">/home/hvn/saltlab/pillars</span>
</code></pre></div>
<p>File <code>roster</code> chứa thông tin về các máy minion:</p>
<div class="highlight"><pre><span></span><code><span class="nt">tv</span><span class="p">:</span>
<span class="nt">host</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">192.168.0.110</span>
<span class="nt">user</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">saltuser</span>
<span class="nt">sudo</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">True</span>
<span class="nt">trau</span><span class="p">:</span>
<span class="nt">host</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">103.x.y.z</span>
<span class="nt">user</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">root</span>
<span class="nt">port</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">22022</span>
</code></pre></div>
<p>ở đây định nghĩa 2 minion, 1 tên <code>tv</code> và 1 tên <code>trau</code>, minion <code>tv</code> sẽ là đối
tượng của bài này.</p>
<p>File này tương tự file inventory (hay có tên là <code>hosts</code>) của Ansible.</p>
<p>Cấu trúc thư mục trông như sau</p>
<div class="highlight"><pre><span></span><code>saltlab/
├── master
├── roster
├── pillars
│ ├── common.sls
│ ├── top.sls
│ └── uds.sls
└── states
├── example.sls
├── htop.sls
├── template.j2
├── uds.sls
└── uds.systemd
</code></pre></div>
<h2>Chạy câu lệnh Salt</h2>
<p>Từ master, trong venv saltenv, gõ lệnh <code>salt-ssh</code> với đối tượng <code>tv</code>, câu lệnh
salt <code>cmd.run</code>, câu lệnh chạy trên <code>tv</code> là <code>uname -a</code>:</p>
<div class="highlight"><pre><span></span><code><span class="o">(</span>saltenv<span class="o">)</span> $ salt-ssh --config ~/saltlab tv cmd.run <span class="s1">'uname -a'</span>
tv:
Linux MINIPC-PN50 <span class="m">5</span>.8.0-55-generic <span class="c1">#62~20.04.1-Ubuntu SMP Wed Jun 2 08:55:04 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux</span>
</code></pre></div>
<h2>Apply Salt state</h2>
<p>Saltstate file là <a href="https://docs.saltproject.io/en/latest/topics/yaml/index.html">file YAML</a> có đuôi <code>.sls</code>, khai báo (declare)
trạng thái của hệ thống mong muốn đạt được. File <code>htop.sls</code></p>
<div class="highlight"><pre><span></span><code><span class="nt">install htop</span><span class="p">:</span> <span class="c1"># ID của "state"</span>
<span class="nt">pkg.installed</span><span class="p">:</span> <span class="c1"># loại state - tương ứng với 1 python function</span>
<span class="p p-Indicator">-</span> <span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">htop</span> <span class="c1"># các tham số - function argument</span>
</code></pre></div>
<p>Cú pháp YAML trên tương ứng với Python dict sau:</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span><span class="s1">'install htop'</span><span class="p">:</span>
<span class="p">{</span><span class="s1">'pkg.installed'</span><span class="p">:</span> <span class="p">[{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'htop'</span><span class="p">}]}}</span>
</code></pre></div>
<p>Sau khi được <code>apply</code>, state trên sẽ cài package <code>htop</code> lên máy, sử dụng package
manager của máy minion, dù là apt trên Ubuntu hay yum/dnf trên Fedora.</p>
<p>Đây là một ưu điểm lớn của Salt, người dùng thậm chí không biết chính xác
câu lệnh để cài thế nào, chỉ cần liệt kê ra những gì mình cần, Salt sẽ lo tất.
Phần này nghe rất "magic", thực chất thì bên dưới pkg là 1 <a href="https://github.com/saltstack/salt/blob/master/salt/states/pkg.py">Python module</a>
còn <a href="https://github.com/saltstack/salt/blob/v3003/salt/states/pkg.py#L1007"><code>installed</code> là 1 function</a>
chạy đủ loại if/else kiểm tra hệ thống và chọn <code>apt</code> hay <code>yum</code> cho phù hợp.
Salt gọi đây là <a href="https://docs.saltproject.io/en/latest/ref/states/all/index.html"><code>state module pkg</code></a>.</p>
<p>Gõ trên master:</p>
<div class="highlight"><pre><span></span><code>$ salt-ssh --config ~/saltlab tv state.apply htop
tv:
----------
ID: install htop
Function: pkg.installed
Name: htop
Result: True
Comment: The following packages were installed/updated: htop
Started: <span class="m">15</span>:09:22.550836
Duration: <span class="m">10969</span>.17 ms
Changes:
----------
htop:
----------
new:
<span class="m">2</span>.2.0-2build1
old:
Summary <span class="k">for</span> tv
------------
Succeeded: <span class="m">1</span> <span class="o">(</span><span class="nv">changed</span><span class="o">=</span><span class="m">1</span><span class="o">)</span>
Failed: <span class="m">0</span>
------------
Total states run: <span class="m">1</span>
</code></pre></div>
<p>Xem đầy đủ các option, các function khác của <code>pkg state module</code> tại <a href="https://docs.saltproject.io/en/latest/ref/states/all/salt.states.pkg.html#module-salt.states.pkg">module-salt.states.pkg</a></p>
<h3>Copy/render file và chạy câu lệnh</h3>
<p>File <code>example.sls</code></p>
<div class="highlight"><pre><span></span><code><span class="nt">render a config file</span><span class="p">:</span>
<span class="nt">file.managed</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="nt">source</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">salt://template.j2</span>
<span class="p p-Indicator">-</span> <span class="nt">template</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">jinja</span>
<span class="p p-Indicator">-</span> <span class="nt">mode</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">0400</span>
<span class="p p-Indicator">-</span> <span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">/tmp/ahihi.yml</span>
<span class="nt">now run a command</span><span class="p">:</span>
<span class="nt">cmd.run</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="nt">name</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">cat /tmp/ahihi.yml | head</span>
</code></pre></div>
<p><code>cmd.run</code> dùng để chạy 1 câu lệnh trên minion, lệnh tùy ý. Với <code>cmd.run</code>,
ta đã có thể làm được mọi thứ, đây là cách đơn giản nhất để thay thế các
bash script mặc dù không phải tối ưu nhất. Ví dụ: nếu chạy apt-get install để
cài package thay vì pkg.installed sẽ mất đi các lợi ích pkg.installed thực hiện
- chạy trên nhiều OS khác nhau, tự động chạy apt update).
Người mới tập dùng Salt có thể dùng <code>cmd.run</code> để bắt đầu, nhưng nên tìm state
module có sẵn để thu được kết quả tốt hơn.</p>
<p><code>file.managed</code> có thể tải 1 file từ internet, có thể copy 1 file từ X đến Y,
trong ví dụ này nó copy file từ salt://template.j2 tới minion rồi render với
<a href="https://docs.saltproject.io/en/getstarted/config/jinja.html">Jinja2 template</a>,
ghi vào <code>/tmp/ahihi.yml</code> và chmod 0400.</p>
<p><code>salt://</code> trong ví dụ này là <code>file_roots</code> trong file cấu hình master, tức thư
mục <code>/home/hvn/saltlab/states</code> trên máy master.</p>
<p>Nội dung file template.j2:</p>
<div class="highlight"><pre><span></span><code><span class="cp">{%</span>- <span class="k">for</span> <span class="nv">i</span> <span class="k">in</span> <span class="o">[</span><span class="s1">'meo'</span><span class="o">,</span> <span class="s1">'bo'</span><span class="o">,</span> <span class="s1">'ga'</span><span class="o">]</span> <span class="cp">%}</span><span class="x"></span>
<span class="x"> - </span><span class="cp">{{</span> <span class="nv">i</span> <span class="cp">}}</span><span class="x"></span>
<span class="cp">{%</span>- <span class="k">endfor</span> <span class="cp">%}</span><span class="x"></span>
<span class="x">myuser: user</span>
<span class="x">mypassword: passwd</span>
<span class="x">os: </span><span class="cp">{{</span> <span class="nv">grains</span><span class="o">[</span><span class="s1">'osfinger'</span><span class="o">]</span> <span class="cp">}}</span><span class="x"></span>
<span class="x">ips: </span><span class="cp">{{</span> <span class="nv">grains</span><span class="o">[</span><span class="s1">'ipv4'</span><span class="o">]</span> <span class="cp">}}</span><span class="x"></span>
</code></pre></div>
<p>Jinja2 có for if/else như Python, xem
<a href="https://docs.saltproject.io/en/getstarted/config/jinja.html">Jinja2 template</a>
để tìm hiểu thêm.</p>
<h3>5 phút dành cho lý thuyết: grains pillar</h3>
<p>1 nhược điểm của Salt là theo mốt thời 2010, đặt tên cho mọi khái niệm, và tên
đó không thực sự có nhiều ý nghĩa - đơn giản chỉ là bịa ra (theo mốt của
<a href="https://www.chef.io/">Chef</a> - một đối thủ viết bằng Ruby).</p>
<p>Salt có 2 khái niệm:</p>
<ul>
<li>grains: chứa các thông tin về máy minion (OS, version, ip, ...)</li>
<li>pillar: chứa các thông tin truyền từ master (thường là các thông tin bí mật như
user/password).</li>
</ul>
<h4>salt grains</h4>
<p>Ví dụ trên có truy cập 2 thông tin từ grains là tên hệ điều hành và các IPv4 của
máy minion. state.apply</p>
<div class="highlight"><pre><span></span><code> <span class="nt">ID</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">render a config file</span>
<span class=" -Error"> </span><span class="nt">Function</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">file.managed</span>
<span class="l l-Scalar l-Scalar-Plain">Name</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">/tmp/ahihi.yml</span>
<span class="l l-Scalar l-Scalar-Plain">Result</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">True</span>
<span class="l l-Scalar l-Scalar-Plain">Comment</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">File /tmp/ahihi.yml is in the correct state</span>
<span class="l l-Scalar l-Scalar-Plain">Started</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">19:33:25.337228</span>
<span class="nt">Duration</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">34.549 ms</span>
<span class="l l-Scalar l-Scalar-Plain">Changes</span><span class="p p-Indicator">:</span>
<span class="l l-Scalar l-Scalar-Plain">----------</span>
<span class="l l-Scalar l-Scalar-Plain">ID</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">now run a command</span>
<span class="l l-Scalar l-Scalar-Plain">Function</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">cmd.run</span>
<span class="l l-Scalar l-Scalar-Plain">Name</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">cat /tmp/ahihi.yml | head</span>
<span class="l l-Scalar l-Scalar-Plain">Result</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">True</span>
<span class="l l-Scalar l-Scalar-Plain">Comment</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">Command "cat /tmp/ahihi.yml | head" run</span>
<span class="l l-Scalar l-Scalar-Plain">Started</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">19:33:25.372408</span>
<span class="l l-Scalar l-Scalar-Plain">Duration</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">7.672 ms</span>
<span class="l l-Scalar l-Scalar-Plain">Changes</span><span class="p p-Indicator">:</span>
<span class="l l-Scalar l-Scalar-Plain">----------</span>
<span class="l l-Scalar l-Scalar-Plain">pid</span><span class="p p-Indicator">:</span>
<span class="l l-Scalar l-Scalar-Plain">42905</span>
<span class="nt">retcode</span><span class="p">:</span>
<span class="l l-Scalar l-Scalar-Plain">0</span>
<span class="nt">stderr</span><span class="p">:</span>
<span class="nt">stdout</span><span class="p">:</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">meo</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">bo</span>
<span class="p p-Indicator">-</span> <span class="l l-Scalar l-Scalar-Plain">ga</span>
<span class=" -Error"> </span><span class="nt">myuser</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">user</span>
<span class="nt">mypassword</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">passwd</span>
<span class="nt">os</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">Ubuntu-20.04</span>
<span class="nt">ips</span><span class="p">:</span> <span class="p p-Indicator">[</span><span class="s">'127.0.0.1'</span><span class="p p-Indicator">,</span> <span class="s">'192.168.0.110'</span><span class="p p-Indicator">]</span>
</code></pre></div>
<p>Để liệt kê tất cả grains của minion, dùng Salt command <code>grains.items</code></p>
<div class="highlight"><pre><span></span><code>$ salt-ssh -c ~/saltlab tv grains.items
tv:
----------
biosreleasedate:
<span class="m">08</span>/27/2020
biosversion:
<span class="m">0416</span>
cpu_flags:
...
</code></pre></div>
<h4>salt pillar</h4>
<p>Pillar chỉ nằm trên master, nó map file nào dành cho minion nào. File <code>top.sls</code></p>
<div class="highlight"><pre><span></span><code>base:
'*':
- common
'trau':
- uds
</code></pre></div>
<p><code>*</code> tức mọi minion đều có thể truy cập các pillar item trong file <code>common.sls</code>,
chỉ <code>trau</code> mới truy cập được nội dung trong file <code>uds.sls</code>.</p>
<p>Các file pillar là các file <code>.sls</code> theo syntax YAML, biểu diễn các dictionary
của Python. Ví dụ: <code>uds.sls</code>:</p>
<div class="highlight"><pre><span></span><code><span class="nt">telegram_token</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">this_is_token</span>
<span class="nt">database</span><span class="p">:</span>
<span class="nt">username</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">root</span>
<span class="nt">password</span><span class="p">:</span> <span class="l l-Scalar l-Scalar-Plain">toor</span>
</code></pre></div>
<p>Để liệt kê tất cả pillar item dành cho minion, dùng Salt command <code>pillar.items</code>
(chú ý grains có s, pillar không có).</p>
<div class="highlight"><pre><span></span><code>$ salt-ssh -c ~/saltlab trau pillar.items
trau:
----------
database:
----------
password:
toor
username:
root
telegram_token:
this_is_token
</code></pre></div>
<p>Ví dụ 1 file dùng pillar:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># /lib/systemd/system/uds.service</span>
<span class="p p-Indicator">[</span><span class="nv">Unit</span><span class="p p-Indicator">]</span>
<span class="l l-Scalar l-Scalar-Plain">Description=UDS telegram bot</span>
<span class="l l-Scalar l-Scalar-Plain">[Service]</span>
<span class="l l-Scalar l-Scalar-Plain">User=uds</span>
<span class="l l-Scalar l-Scalar-Plain">Environment=BOT_TOKEN={{ pillar['telegram_token'] }}</span>
</code></pre></div>
<h3>Deploy 1 Python project</h3>
<p>Link này viết 1 salt formula (tên của 1 "bộ cài bằng Salt") để cài
bot telegram <code>uds</code> chạy trên Ubuntu 18.04 với systemd.
<a href="https://github.com/pymivn/udsbot-salt">udsbot-salt</a></p>
<h3>Troubleshooting</h3>
<p>Khi kêt quả chạy apply không thành công, thêm <code>-ldebug</code> để xem log chi tiết.</p>
<h2>Dùng Ansible "xịn hơn" không?</h2>
<p>Không, Ansible giống Salt đến bất ngờ, cũng viết bằng Python với các module
tương ứng, cũng dùng Jinja2, cũng viết cấu hình bằng file YAML.</p>
<p><a href="https://github.com/hvnsweeting/elbisna/blob/master/redis/roles/redis/tasks/main.yml">Xem ví dụ</a>.</p>
<p>Ngày nay, Ansible phổ biến hơn nhờ nó đơn giản hơn để bắt đầu, do ít
từ khóa hay khái niệm lạ. Năm 2015, RedHat mua lại Ansible khiến cho nó
càng trở nên phổ biến hơn.</p>
<p>Salt thành công trước Ansible (2012 2013), đứng sau là công ty SaltStack, đến
năm 2020 cũng được ông lớn <a href="https://www.vmware.com/support/acquisitions/saltstack.html">VMWare
mua lại</a>. Salt có
nhiều ưu điểm so với Ansible (đặc biệt là nhanh),
cung cấp nhiều tính năng phức tạp - dùng trong môi trường phức tạp.</p>
<p>Dùng Salt chuyển sang Ansible mất nửa ngày để map lại khái niệm.</p>
<h2>Học salt là học gì</h2>
<p>Đa phần là học các salt module (như <code>pkg.installed</code>) có sẵn để viết các
state, dùng chúng như các viên gạch để tự động quá trình cài đặt / cấu hình
phần mềm trên hệ thống.
Cách thực hành đơn giản nhất là dùng Salt cài các phần mềm trên chính máy tính
của mình đang dùng, thay vì gọi <code>salt-ssh</code>, dùng</p>
<div class="highlight"><pre><span></span><code>sudo salt-call --local state.apply FORMULA_NAME -linfo
</code></pre></div>
<h2>Hành động của chúng ta</h2>
<p>Tạo 1 salt formula để deploy app Flask hello world chạy bằng gunicorn
với NGINX server rồi tạo Pull Request vào <a href="https://github.com/pymivn/flask-salt">pymivn/flask-salt</a>
để trăm hay đều như tay quen.</p>
<h2>Kết luận</h2>
<p>Năm COVID-19 thứ 2, 2021, Salt, Ansible, Chef hay Puppet không còn mới mẻ gì,
từng là "điểm cộng" trong các vòng tuyển dụng thì giờ là yêu cầu hiển nhiên
cho giới "DevOps", thế giới cũng đã đu theo một trend mới hơn, hot hơn
có tên Docker+Kubernetes, nhưng các CM vẫn luôn có đất dùng. Trend lên rồi sẽ
xuống, hot rồi sẽ nguội, cái gì hợp lý thì ta dùng.</p>
<p>Đến đây, đã đủ để các Python dev deploy code như 1 DevOps engineer thực thụ,
không phải nhọc nhằn code bash.</p>
<blockquote>
<p>Life's too short to remember how to write Bash code. I feel liberated. — @laheadle on Clojurians Slack</p>
</blockquote>
<h2>References</h2>
<ul>
<li><a href="https://docs.saltproject.io/en/latest/topics/ssh/">salt-ssh</a></li>
<li>https://github.com/saltstack/salt/issues/59942</li>
<li><a href="https://www.familug.org/2015/06/saltstack-chao-muoi-em-la-ai.html">Chào Muối, em là ai? </a></li>
<li>https://docs.saltproject.io/en/latest/contents.html</li>
<li>Các formula cộng đồng <a href="https://github.com/saltstack-formulas">saltstack-formulas</a></li>
</ul>
<h3>Ủng hộ tác giả</h3>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>.</p>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
</ul>Học Haskell không phải trầm trồ - theo cách Pymi.vn2021-06-10T00:00:00+07:002021-06-10T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2021-06-10:/article/haskell1/<p>ngôn ngữ lập trình luôn được xếp vào nhóm "khó học nhất" - giống Python bất ngờ - phần 1.</p><ul>
<li>Cảnh báo: rất giống Python</li>
<li>Chú ý: không cần biết Python</li>
</ul>
<p>Haskell (/ˈhæskəl/) - visub: ha-s-kồ - 1990 (lớn hơn Python 1 tuổi)
Trang chủ: <a href="https://www.haskell.org/">https://www.haskell.org/</a></p>
<p><img alt="Haskell" src="https://www.haskell.org/img/haskell-logo.svg"></p>
<p>Haskell là ngôn ngữ functional (lập trình hàm), thuộc nhóm "pure" blah blah blah
có thể bỏ qua và gõ cho đến cuối bài, work first, talk is cheap, later.</p>
<h2>Cài đặt</h2>
<p>Hướng dẫn <a href="https://www.haskell.org/downloads/#linux-mac-freebsd">trang chủ</a>, hỗ trợ cả Windows</p>
<div class="highlight"><pre><span></span><code>curl --proto <span class="s1">'=https'</span> --tlsv1.2 -sSf https://get-ghcup.haskell.org <span class="p">|</span> sh
</code></pre></div>
<p>enter enter enter ... rồi mở terminal mới, gõ <code>ghci</code>.</p>
<div class="highlight"><pre><span></span><code>ghci --version
The Glorious Glasgow Haskell Compilation System, version 8.10.5
</code></pre></div>
<p>Trên Ubuntu có thể dùng:</p>
<div class="highlight"><pre><span></span><code>sudo apt-get update <span class="o">&&</span> sudo apt-get install -y haskell-stack
</code></pre></div>
<h2>REPL</h2>
<p>REPL - Read Eval Print Loop, là chương trình nhận code người dùng nhập vào
(Read), chạy code đó (Eval), in kết quả ra màn hình (Print), và cứ tiếp tục vậy
(Loop).</p>
<p>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.</p>
<p>Việc viết code khi dùng các ngôn ngữ có REPL thường theo các bước:</p>
<ul>
<li>bật REPL lên</li>
<li>gõ code thử cho tới khi thu được kết quả mong muốn</li>
<li>copy code đó vào editor/IDE</li>
</ul>
<p>Câu lệnh bật REPL của Haskell có tên <code>ghci</code>.</p>
<div class="highlight"><pre><span></span><code><span class="o">$</span> <span class="n">ghci</span>
<span class="kt">GHCi</span><span class="p">,</span> <span class="n">version</span> <span class="mf">8.10</span><span class="o">.</span><span class="mi">5</span><span class="kt">:</span> <span class="n">https</span><span class="kt">://</span><span class="n">www</span><span class="o">.</span><span class="n">haskell</span><span class="o">.</span><span class="n">org</span><span class="o">/</span><span class="n">ghc</span><span class="o">/</span> <span class="kt">:?</span> <span class="n">for</span> <span class="n">help</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">1</span>
<span class="mi">2</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mi">2</span> <span class="o">*</span> <span class="mi">1024</span>
<span class="mi">2048</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">:</span><span class="n">quit</span>
<span class="kt">Leaving</span> <span class="kt">GHCi</span><span class="o">.</span>
</code></pre></div>
<h2>Haskell Hello world</h2>
<div class="highlight"><pre><span></span><code>Prelude> print <span class="s2">"Hello Pymier!"</span>
<span class="s2">"Hello Pymier!"</span>
</code></pre></div>
<h2>Integer</h2>
<p>cộng <code>+</code> trừ <code>-</code> nhân <code>*</code> mũ/lũy thừa <code>^</code></p>
<div class="highlight"><pre><span></span><code>Prelude> <span class="m">54</span> + <span class="m">5</span> * <span class="o">(</span><span class="m">2</span> + <span class="m">1</span><span class="o">)</span>
<span class="m">69</span>
Prelude> <span class="m">2</span> ^ <span class="m">1000</span>
<span class="m">10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376</span>
</code></pre></div>
<h2>Float</h2>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="mi">4</span> <span class="o">/</span> <span class="mi">2</span>
<span class="mf">2.0</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mi">5</span> <span class="o">/</span> <span class="mi">2</span>
<span class="mf">2.5</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mi">5</span> <span class="o">/</span> <span class="mf">2.5</span>
<span class="mf">2.0</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mf">0.1</span> <span class="o">+</span> <span class="mf">0.1</span> <span class="o">+</span> <span class="mf">0.1</span>
<span class="mf">0.30000000000000004</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mf">0.1</span> <span class="o">+</span> <span class="mf">0.1</span> <span class="o">+</span> <span class="mf">0.1</span> <span class="o">==</span> <span class="mf">0.3</span>
<span class="kt">False</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mf">0.1</span> <span class="o">+</span> <span class="mf">0.1</span> <span class="o">+</span> <span class="mf">0.1</span> <span class="o">/=</span> <span class="mf">0.3</span>
<span class="kt">True</span>
</code></pre></div>
<p><a href="https://pymi.vn/blog/why-not-float/">Tại sao 0.1 + 0.1 + 0.1 /= 0.3</a></p>
<h2>Boolean</h2>
<div class="highlight"><pre><span></span><code>Prelude> <span class="m">2</span> < <span class="m">5</span>
True
Prelude> <span class="m">2</span> > <span class="m">5</span>
False
Prelude> <span class="m">1</span> + <span class="nv">1</span> <span class="o">==</span> <span class="m">2</span>
True
Prelude> <span class="m">2</span> - <span class="m">1</span> /<span class="o">=</span> <span class="m">0</span>
True
Prelude> <span class="m">2</span> <<span class="o">=</span> <span class="m">2</span>
True
Prelude> <span class="m">1</span>/0
Infinity
</code></pre></div>
<p>and</p>
<div class="highlight"><pre><span></span><code>Prelude> True <span class="o">&&</span> True
True
Prelude> True <span class="o">&&</span> False
False
Prelude> False <span class="o">&&</span> True
False
Prelude> False <span class="o">&&</span> False
False
</code></pre></div>
<p>or</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="kt">True</span> <span class="o">||</span> <span class="kt">True</span>
<span class="kt">True</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">True</span> <span class="o">||</span> <span class="kt">False</span>
<span class="kt">True</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">False</span> <span class="o">||</span> <span class="kt">True</span>
<span class="kt">True</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">False</span> <span class="o">||</span> <span class="kt">False</span>
<span class="kt">False</span>
</code></pre></div>
<p>not</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="n">not</span> <span class="kt">True</span>
<span class="kt">False</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="n">not</span> <span class="kt">False</span>
<span class="kt">True</span>
</code></pre></div>
<p>Haskell boolean có tính <a href="https://pymi.vn/tutorial/boolean/">short-circuit</a> -
dừng lại ngay khi có thể kết luận kết quả biểu thức boolean.</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="ne">error</span> <span class="s">"huhu"</span>
<span class="o">***</span> <span class="kt">Exception:</span> <span class="n">huhu</span>
<span class="kt">CallStack</span> <span class="p">(</span><span class="n">from</span> <span class="kt">HasCallStack</span><span class="p">)</span><span class="kt">:</span>
<span class="ne">error</span><span class="p">,</span> <span class="n">called</span> <span class="n">at</span> <span class="o"><</span><span class="n">interactive</span><span class="o">>:</span><span class="mi">2</span><span class="kt">:</span><span class="mi">1</span> <span class="kr">in</span> <span class="n">interactive</span><span class="kt">:Ghci2</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">True</span> <span class="o">||</span> <span class="ne">error</span> <span class="s">"huhu"</span>
<span class="kt">True</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">False</span> <span class="o">&&</span> <span class="ne">error</span> <span class="s">"huhu"</span>
<span class="kt">False</span>
</code></pre></div>
<p>hay nói cụ thể:</p>
<ul>
<li><code>||</code> sẽ dừng lại và trả về kết quả ngay khi gặp <code>True</code></li>
<li><code>&&</code> sẽ dừng lại và trả về kết quả ngay khi gặp <code>False</code></li>
</ul>
<p>Với các ngôn ngữ khác, boolean có short-circuit hay không thì tùy từng ngôn
ngữ quyết định, nhưng với một tính năng nổi bật của Haskell: <strong>Lazy</strong>,
short-circuit là chuyện đương nhiên.</p>
<h2>type</h2>
<p>Các câu lệnh trong <code>ghci</code></p>
<ul>
<li><code>:help</code></li>
<li><code>:info</code></li>
</ul>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="kt">:</span><span class="n">info</span> <span class="n">mod</span>
<span class="kr">type</span> <span class="kt">Integral</span> <span class="ow">::</span> <span class="o">*</span> <span class="ow">-></span> <span class="kt">Constraint</span>
<span class="kr">class</span> <span class="p">(</span><span class="kt">Real</span> <span class="n">a</span><span class="p">,</span> <span class="kt">Enum</span> <span class="n">a</span><span class="p">)</span> <span class="ow">=></span> <span class="kt">Integral</span> <span class="n">a</span> <span class="kr">where</span>
<span class="o">...</span>
<span class="n">mod</span> <span class="ow">::</span> <span class="n">a</span> <span class="ow">-></span> <span class="n">a</span> <span class="ow">-></span> <span class="n">a</span>
<span class="o">...</span>
<span class="c1">-- Defined in ‘GHC.Real’</span>
<span class="kr">infixl</span> <span class="mi">7</span> <span class="p">`</span><span class="n">mod</span><span class="p">`</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="kt">:</span><span class="n">info</span> <span class="p">(</span><span class="o">+</span><span class="p">)</span>
<span class="kr">type</span> <span class="kt">Num</span> <span class="ow">::</span> <span class="o">*</span> <span class="ow">-></span> <span class="kt">Constraint</span>
<span class="kr">class</span> <span class="kt">Num</span> <span class="n">a</span> <span class="kr">where</span>
<span class="p">(</span><span class="o">+</span><span class="p">)</span> <span class="ow">::</span> <span class="n">a</span> <span class="ow">-></span> <span class="n">a</span> <span class="ow">-></span> <span class="n">a</span>
<span class="o">...</span>
<span class="c1">-- Defined in ‘GHC.Num’</span>
<span class="kr">infixl</span> <span class="mi">6</span> <span class="o">+</span>
</code></pre></div>
<p>Hiển thị type:</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="kt">:</span><span class="n">set</span> <span class="o">+</span><span class="n">t</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mi">1</span>
<span class="mi">1</span>
<span class="nf">it</span> <span class="ow">::</span> <span class="kt">Num</span> <span class="n">p</span> <span class="ow">=></span> <span class="n">p</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="mf">0.1</span>
<span class="mf">0.1</span>
<span class="nf">it</span> <span class="ow">::</span> <span class="kt">Fractional</span> <span class="n">p</span> <span class="ow">=></span> <span class="n">p</span>
</code></pre></div>
<p>số nguyên 1 có kiểu <code>Num</code>, số float <code>0.1</code> có kiểu <code>Fractional</code></p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="kt">True</span>
<span class="kt">True</span>
<span class="nf">it</span> <span class="ow">::</span> <span class="kt">Bool</span>
</code></pre></div>
<p>giá trị boolean True có kiểu <code>Bool</code></p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="s">"con mèo trèo"</span>
<span class="s">"con m</span><span class="se">\232</span><span class="s">o tr</span><span class="se">\232</span><span class="s">o"</span>
<span class="nf">it</span> <span class="ow">::</span> <span class="p">[</span><span class="kt">Char</span><span class="p">]</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="n">length</span> <span class="s">"con mèo trèo"</span>
<span class="mi">12</span>
<span class="nf">it</span> <span class="ow">::</span> <span class="kt">Int</span>
</code></pre></div>
<p>string là 1 list của các <code>Char</code> (character - ký tự)</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="mi">10</span><span class="p">]</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">10</span><span class="p">]</span>
<span class="nf">it</span> <span class="ow">::</span> <span class="p">(</span><span class="kt">Num</span> <span class="n">a</span><span class="p">,</span> <span class="kt">Enum</span> <span class="n">a</span><span class="p">)</span> <span class="ow">=></span> <span class="p">[</span><span class="n">a</span><span class="p">]</span>
</code></pre></div>
<p>kiểu list được biểu diễn bởi dấu <code>[]</code></p>
<p><code>it</code> là biểu thức/kết quả cuối cùng bạn đã gõ. Tương tự <code>_</code> trong Python interpreter.</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="mi">1</span>
<span class="mi">1</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="n">it</span> <span class="o">+</span> <span class="mi">2</span>
<span class="mi">3</span>
</code></pre></div>
<h2>ProjectEuler problem 1</h2>
<p><a href="https://projecteuler.net/problem=1">https://projecteuler.net/problem=1</a></p>
<p>Chú ý: theo chương trình học của <a href="https://pymi.vn">Pymi.vn</a>, phần này được
học ở buổi số 4. Bạn đọc cần biết Python để hiểu phần này hoặc chỉ cần
gõ theo. Có thể đọc thêm tại <a href="https://pp.pymi.vn/article/tuple_comps/">đây</a>.</p>
<blockquote>
<p>If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23.
Find the sum of all the multiples of 3 or 5 below 1000.</p>
</blockquote>
<p>Tạo list các số từ 1 đến 5</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="mi">5</span><span class="p">]</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">]</span>
</code></pre></div>
<p>Haskell không dùng <code>%</code> cho phép chia lấy phần dư (modulo/remainder), code Python <code>10 % 3</code> tương đương với viết Haskell <code>mod 10 3</code> hoặc
<code>rem 10 3</code></p>
<p>Haskell dùng <code>|| &&</code> thay Python <code>or and</code></p>
<p><a href="https://docs.python.org/3/whatsnew/2.0.html#list-comprehensions">Python 2.0 <strong>MƯỢN</strong> list comprehension từ Haskell</a></p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="nb">sum</span><span class="p">([</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">])</span>
<span class="mi">233168</span>
</code></pre></div>
<p>Haskell dùng <code>|</code> thay chữ <code>for</code>, dùng <code><-</code> thay chữ <code>in</code>, dùng <code>,</code> thay chữ <code>if</code> so với Python</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="n">sum</span> <span class="p">[</span><span class="n">i</span> <span class="o">|</span> <span class="n">i</span> <span class="ow"><-</span> <span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="mi">999</span><span class="p">],</span> <span class="n">mod</span> <span class="n">i</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">mod</span> <span class="n">i</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">]</span>
<span class="mi">233168</span>
</code></pre></div>
<p>Bonus:
bài <a href="https://projecteuler.net/problem=16">PE16</a>:</p>
<blockquote>
<p>Tính tổng các chữ số của 2 mũ 1000</p>
</blockquote>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="n">sum</span> <span class="p">[</span><span class="n">read</span> <span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="ow">::</span> <span class="kt">Integer</span> <span class="o">|</span> <span class="n">i</span> <span class="ow"><-</span> <span class="n">show</span> <span class="p">(</span><span class="mi">2</span><span class="o">^</span><span class="mi">1000</span><span class="p">)]</span>
<span class="mi">1366</span>
</code></pre></div>
<h2>Các đặc tính nổi bật của Haskell</h2>
<p>Theo <a href="https://wiki.haskell.org/Introduction">wiki Haskell</a></p>
<blockquote>
<p>Haskell is a computer programming language. In particular, it is a polymorphically statically typed, lazy, purely functional language, quite different from most other programming languages. The language is named for Haskell Brooks Curry, whose work in mathematical logic serves as a foundation for functional languages. Haskell is based on the lambda calculus, hence the lambda we use as a logo.</p>
</blockquote>
<ul>
<li>polymorphically statically typed</li>
<li>lazy</li>
<li>purely functional</li>
</ul>
<p>hoặc xem phần features trên https://www.haskell.org/</p>
<h3>lập trình hàm là gì</h3>
<p>Haskell là ngôn ngữ thuộc nhóm functional (lập trình hàm).</p>
<p>Trên lý thuyết,
có nghĩa nó dựa trên <a href="https://wiki.haskell.org/Lambda_calculus">"lambda calculus"</a>, một mô hình/hệ thống
tính toán dùng các function (hàm toán học), khác với mô hình Turing Machine mà
các ngôn ngữ lập trình C, Java, Python, Go... dựa trên.
Hai mô hình này được chứng minh về mặt toán học là có khả năng như nhau.</p>
<p>Về mặt thực hành, code với 1 ngôn ngữ functional thường có nghĩa là:</p>
<ul>
<li>không dùng vòng lặp for/while mà dùng các function có sẵn để làm việc tương tự
(vd: map, filter, fold, reduce,...) hoặc viết các <a href="https://pymi.vn/blog/print-recursively/">recursive function</a> để thu được
kết quả tương ứng.</li>
<li>Các kiểu dữ liệu thường là immutable, khi thay đổi 1 list, Haskell sẽ
tạo ra 1 list mới với những thay đổi đã thực hiện (và bỏ list cũ đi).</li>
<li>First class function: quen với việc dùng function này làm đầu vào cho function
khác. Ví dụ map <code>map (\x -> x * 2) [1..5]</code> nhận function <code>\x -> x * 2</code> và 1 list
để thực hiện function đó trên tất cả các phần tử trong list.</li>
</ul>
<h3>Haskell purely functional là gì</h3>
<p>pure function là một function không có "side effect", giống hàm toán học, kết
quả đầu ra chỉ phụ thuộc đầu vào.</p>
<div class="highlight"><pre><span></span><code><span class="n">f</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">=</span> <span class="mi">2</span><span class="n">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">f</span><span class="p">(</span><span class="mi">30</span><span class="p">)</span> <span class="n">luôn</span> <span class="n">luôn</span> <span class="n">bằng</span> <span class="mi">61</span>
</code></pre></div>
<p>Side effect là việc
function thực hiện 1 thay đổi nào đó (thay đổi phần tử 1 list, đọc ghi 1 file,
in ra màn hình, kết nối internet, sleep chương trình, ...) hay phụ thuộc vào
yếu tố khác với đầu vào (một chương trình phụ thuộc vào thời gian lúc nó chạy)
nghe hơi vô lý khi
một ngôn ngữ mà không tương tác với thế giới bên ngoài thì... chỉ để học toán.
Nhưng Haskell sẽ dựa trên 1 khái niệm/cơ chế hoàn toàn khác để thực hiện các
việc nói trên.</p>
<h3>Haskell lazy là gì</h3>
<p>lazy là chỉ thực hiện tính toán khi thực sự cần tới giá trị.
Ví dụ có thể viết code tạo ra list từ 1 tới vô cùng, nhưng vì Haskell lazy,
nó chỉ lấy ra phần tử nó cần, chứ không tạo list từ 1 tới vô cùng từ đầu.</p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="n">take</span> <span class="mi">10</span> <span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="p">]</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">10</span><span class="p">]</span>
<span class="kt">Prelude</span><span class="o">></span> <span class="n">take</span> <span class="mi">20</span> <span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="p">]</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="mi">18</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">20</span><span class="p">]</span>
</code></pre></div>
<p>Trong Python, khái niệm gần nhất với lazy là <a href="https://pp.pymi.vn/article/tuple_comps/"><code>generator</code></a></p>
<p>Để Haskell bắt buộc phải tính list [1..] tới vô cùng, ta dùng 1 function cần
phải duyệt đến phần tử cuối cùng của list - như tính độ dài : <code>length</code></p>
<div class="highlight"><pre><span></span><code><span class="kt">Prelude</span><span class="o">></span> <span class="n">length</span> <span class="p">[</span><span class="mi">1</span><span class="o">..</span><span class="p">]</span>
<span class="o">^</span><span class="kt">CInterrupted</span><span class="o">.</span>
</code></pre></div>
<p>code này sẽ chạy mãi cho tới khi người dùng tắt chương trình hoặc bấm Ctrl-C.</p>
<h2>Kết luận</h2>
<p>Ngày đầu của Haskell không hề khó hơn ngày đầu học Python. Đừng vì "cộng đồng
mạng" nói khó mà chưa thử đã tin!</p>
<h2>Tham khảo</h2>
<ul>
<li>https://pymi.vn/tutorial/python-integer/</li>
<li>https://pymi.vn/tutorial/python-calculation-2/</li>
<li><a href="http://book.realworldhaskell.org/read/getting-started.html">RealWorldHaskell</a></li>
</ul>
<h2>What next?</h2>
<p>Loạt bài viết dự kiến có 6-8 bài, ứng với 6-8 buổi <a href="https://pymi.vn">học Python tại pymi.vn</a></p>
<h4>Ủng hộ tác giả viết phần tiếp theo</h4>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
</ul>Python free variable2021-06-07T00:00:00+07:002021-06-07T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2021-06-07:/article/free/<p>tự do như biến</p><p>Python có 2 loại variable (biến): local, global, và free (đếm từ 0, tất nhiên).</p>
<p><img alt="free" src="https://images.unsplash.com/photo-1546672117-f83291ce87a9?crop=entropy&cs=tinysrgb&fit=max&fm=jpg&ixid=MnwyMzI1MzN8MHwxfHJhbmRvbXx8fHx8fHx8fDE2MjMwNzQxNTI&ixlib=rb-1.2.1&q=80&w=600"></p>
<h2>binding</h2>
<p><code>x = 42</code> trong Python đọc là bind name x tới object 42.
Tham khảo thêm tại bài <a href="https://pymi.vn/blog/call-by/">Python call by gì?</a></p>
<h2>3 loại variable trong Python</h2>
<h3>global variable</h3>
<p>Đoạn code sau</p>
<div class="highlight"><pre><span></span><code><span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">42</span>
</code></pre></div>
<p><code>x</code> viết sát lề, gọi là global variable. Chạy đoạn code trên sẽ hiện ra
exception:</p>
<div class="highlight"><pre><span></span><code><span class="ne">NameError</span><span class="p">:</span> <span class="n">name</span> <span class="s1">'x'</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">defined</span>
</code></pre></div>
<p>do code dùng x trước khi x được bind tới object 42.</p>
<h3>local variable</h3>
<p>Đoạn code tiếp theo, chạy sẽ thấy gì? Gợi ý: không phải NameError:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">42</span>
<span class="n">foo</span><span class="p">()</span>
</code></pre></div>
<p><code>x = 42</code> nằm trong 1 block (trong thân function hay class), gọi là local variable.
Trong 1 block, dùng 1 variable/name trước khi bind nó (tức là có bind, nhưng
bind sau khi dùng), exception sẽ xảy ra là</p>
<div class="highlight"><pre><span></span><code><span class="ne">UnboundLocalError</span><span class="p">:</span> <span class="n">local</span> <span class="n">variable</span> <span class="s1">'x'</span> <span class="n">referenced</span> <span class="n">before</span> <span class="n">assignment</span>
</code></pre></div>
<h3>free variable</h3>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="n">foo</span><span class="p">()</span>
</code></pre></div>
<p>Xóa <code>x = 42</code> trong ví dụ phần local, ta chạy đoạn code này, lại thấy NameError.</p>
<div class="highlight"><pre><span></span><code><span class="ne">NameError</span><span class="p">:</span> <span class="n">name</span> <span class="s1">'x'</span> <span class="ow">is</span> <span class="ow">not</span> <span class="n">defined</span>
</code></pre></div>
<p>Lần này không xảy ra UnboundLocalError, do đoạn code không bind x = 42
trong thân function (block). <code>x</code> ở đây là một free variable.</p>
<p>Free variable hoạt động theo cách ... rất tự do. Khi không tìm thấy x trong
foo, Python sẽ đi tìm x ở global (bên ngoài function foo).</p>
<div class="highlight"><pre><span></span><code><span class="n">x</span> <span class="o">=</span> <span class="mi">42</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">96</span>
<span class="n">foo</span><span class="p">()</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">100</span>
</code></pre></div>
<p>Màn hình sẽ hiện ra <code>42</code>, <code>100</code> hay <code>96</code>?</p>
<p>Việc tính toán tên <code>x</code> có giá trị gì, được thực hiện khi function <strong>CHẠY</strong>.
Khi gọi <code>foo()</code> ở trên, x đã có giá trị là 96.</p>
<h2>Static analysis</h2>
<p>Việc dùng các công cụ (phần mềm/chương trình) để phân tích/tìm lỗi code bằng
cách đọc code (text) - mà không cần chạy code, gọi là static analysis.
Trong Python phổ biến các công cụ như <code>pep8</code>, <code>flake8</code>, <code>pylint</code>, <a href="https://pp.pymi.vn/article/mypy/"><code>mypy</code> cũng
có thể tính luôn</a>, hay các tính năng
tích hợp sẵn của IDE như Pycharm.</p>
<p><code>flake8</code> cơ bản giống <code>pep8</code> (tên mới là <code>pycodestyle</code>), thêm tính năng phát
hiện thư viện không dùng tới/ hay biến không tồn tại.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">math</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">42</span>
<span class="n">foo</span><span class="p">()</span>
</code></pre></div>
<p>khi chạy <code>flake8</code> với file code, <code>flake8</code> sẽ phát hiện ra thư viện
<code>math</code> được import nhưng không dùng, tên <code>x</code> chưa được định nghĩa.</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">flake8</span> <span class="n">scope</span><span class="o">.</span><span class="n">py</span>
<span class="n">scope</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span><span class="mi">1</span><span class="p">:</span> <span class="n">F401</span> <span class="s1">'math'</span> <span class="n">imported</span> <span class="n">but</span> <span class="n">unused</span>
<span class="n">scope</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">5</span><span class="p">:</span><span class="mi">11</span><span class="p">:</span> <span class="n">F821</span> <span class="n">undefined</span> <span class="n">name</span> <span class="s1">'x'</span>
<span class="n">scope</span><span class="o">.</span><span class="n">py</span><span class="p">:</span><span class="mi">6</span><span class="p">:</span><span class="mi">5</span><span class="p">:</span> <span class="n">F841</span> <span class="n">local</span> <span class="n">variable</span> <span class="s1">'x'</span> <span class="ow">is</span> <span class="n">assigned</span> <span class="n">to</span> <span class="n">but</span> <span class="n">never</span> <span class="n">used</span>
</code></pre></div>
<p><code>flake8</code> rất hữu ích khi dễ dàng phát hiện các lỗi đơn giản như gõ nhầm tên
biến hay dùng biến không tồn tại như trên.
Nhưng khi <code>x</code> là free variable, không công cụ nào của Python có thể phát hiện
ra lỗi này cho tới khi chạy mới thấy exception:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">n_pymi_vn</span><span class="p">()</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">s</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">n_pymi_vn</span><span class="p">()</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">10</span>
<span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
</code></pre></div>
<p>Do <code>x</code> là free variable, các công cụ phải quét hết cả file code để tìm <code>x</code>,
và nó tìm thấy, nên tin rằng <code>x</code> có tồn tại, nhưng đã quá muộn rồi.
Một ví dụ vô lý hơn nữa, để thấy sự bất lực của các công cụ static analysis:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">n_pymi_vn</span><span class="p">()</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">s</span>
<span class="k">if</span> <span class="mi">1</span> <span class="o">></span> <span class="mi">10</span><span class="p">:</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">10</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">n_pymi_vn</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
</code></pre></div>
<h2>Hành động của chúng ta</h2>
<p>Hạn chế hết mức việc sử dụng global variable, free variable, cần biến gì thì
đưa argument vào function biến đó, code "sát tường" cho hết vào 1 function
main và gọi main() nếu cần chạy.</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">n_pymi_vn</span><span class="p">()</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">s</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">n_pymi_vn</span><span class="p">()</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">10</span>
<span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p><code>flake8</code> sẽ làm tốt nhiệm vụ phát hiện ra <code>x</code> chưa được định nghĩa trong ví dụ trên:
<code>F821 undefined name 'x'</code>, và đoạn code này có thể viết lại để không
còn dùng global/free variable nữa:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">n_pymi_vn</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">s</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">x</span> <span class="o">=</span> <span class="mi">10</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">n_pymi_vn</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">r</span><span class="p">)</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<h2>Kết luận</h2>
<p>Tính dynamic của Python khiến các công cụ khó có thể xử lý mọi trường hợp,
công cụ sẽ chỉ giúp một phần, phần còn lại là sự cẩn thận của lập trình viên.</p>
<p>Tự do phải chăng cần trong khuôn khổ?</p>
<h2>Tham khảo</h2>
<ul>
<li><a href="https://www.familug.org/p/ung-ho.html">Ủng hộ tác giả 🍺</a></li>
<li><a href="https://docs.python.org/3/reference/executionmodel.html#interaction-with-dynamic-features">https://docs.python.org/3/reference/executionmodel.html#interaction-with-dynamic-features</a></li>
</ul>Cứ đi là đến (go)2021-03-11T00:00:00+07:002021-03-11T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2021-03-11:/article/go12/<p>Học vừa đủ Golang để nguy hiểm - phần 2 - hết</p><p>Phần tiếp theo của loạt bài viết <a href="http://pp.pymi.vn/article/go/"><strong>Học vừa đủ Golang để nguy hiểm</strong></a>.</p>
<p>Chi tiết về các khái niệm chỉ có trong Go mà không có trong Python như Pointer,
sự khác biệt về cách tổ chức package trong Go, declaration & initialization
(khai báo và khởi tạo variable), cùng các standard library quan trọng nhất
cho một SysAdmin/DevOps.</p>
<p>Code trong loạt bài này lược bỏ phần package/import/declare function main, người đọc tự thêm vào để chạy.</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="s">"fmt"</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">"pp.pymi.vn"</span><span class="p">)</span>
<span class="c1">// WRITE CODE HERE</span>
<span class="p">}</span>
</code></pre></div>
<h2>Extended Backus-Naur Form (EBNF)</h2>
<p>Cú pháp dùng để mô tả Go syntax có tên <a href="https://golang.org/ref/spec#Notation">EBNF</a>.</p>
<p>Có vẻ không dễ đọc, nhưng không phải là không thể đọc nổi, bỏ qua nếu
bạn không quan tâm. Một ví dụ:</p>
<div class="highlight"><pre><span></span><code><span class="nx">Production</span> <span class="p">=</span> <span class="nx">production_name</span> <span class="s">"="</span> <span class="p">[</span> <span class="nx">Expression</span> <span class="p">]</span> <span class="s">"."</span> <span class="p">.</span>
<span class="nx">Expression</span> <span class="p">=</span> <span class="nx">Alternative</span> <span class="p">{</span> <span class="s">"|"</span> <span class="nx">Alternative</span> <span class="p">}</span> <span class="p">.</span>
<span class="nx">Alternative</span> <span class="p">=</span> <span class="nx">Term</span> <span class="p">{</span> <span class="nx">Term</span> <span class="p">}</span> <span class="p">.</span>
<span class="nx">Term</span> <span class="p">=</span> <span class="nx">production_name</span> <span class="p">|</span> <span class="nx">token</span> <span class="p">[</span> <span class="s">"…"</span> <span class="nx">token</span> <span class="p">]</span> <span class="p">|</span> <span class="nx">Group</span> <span class="p">|</span> <span class="nx">Option</span> <span class="p">|</span> <span class="nx">Repetition</span> <span class="p">.</span>
<span class="nx">Group</span> <span class="p">=</span> <span class="s">"("</span> <span class="nx">Expression</span> <span class="s">")"</span> <span class="p">.</span>
<span class="nx">Option</span> <span class="p">=</span> <span class="s">"["</span> <span class="nx">Expression</span> <span class="s">"]"</span> <span class="p">.</span>
<span class="nx">Repetition</span> <span class="p">=</span> <span class="s">"{"</span> <span class="nx">Expression</span> <span class="s">"}"</span> <span class="p">.</span>
</code></pre></div>
<p><a href="https://docs.python.org/3.9/reference/grammar.html">Python 3.9 dùng EBNF kết hợp với PEG</a></p>
<h2>Khai báo và khởi tạo</h2>
<p>Declaration & initialization</p>
<div class="highlight"><pre><span></span><code><span class="nx">VarDecl</span> <span class="p">=</span> <span class="s">"var"</span> <span class="p">(</span> <span class="nx">VarSpec</span> <span class="p">|</span> <span class="s">"("</span> <span class="p">{</span> <span class="nx">VarSpec</span> <span class="s">";"</span> <span class="p">}</span> <span class="s">")"</span> <span class="p">)</span> <span class="p">.</span>
<span class="nx">VarSpec</span> <span class="p">=</span> <span class="nx">IdentifierList</span> <span class="p">(</span> <span class="nx">Type</span> <span class="p">[</span> <span class="s">"="</span> <span class="nx">ExpressionList</span> <span class="p">]</span> <span class="p">|</span> <span class="s">"="</span> <span class="nx">ExpressionList</span> <span class="p">)</span> <span class="p">.</span>
</code></pre></div>
<p>Để sử dụng 1 biến (variable) trong Go, cần làm 2 bước declaration (khai báo) và
initialization (khởi tạo giá trị).</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span> <span class="nx">x</span> <span class="kt">int</span>
<span class="kd">var</span> <span class="p">(</span>
<span class="nx">y</span> <span class="kt">bool</span>
<span class="nx">z</span> <span class="kt">float64</span>
<span class="p">)</span>
<span class="nb">println</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">z</span><span class="p">)</span>
<span class="c1">// Output</span>
<span class="c1">// 0 false +0.000000e+000</span>
</code></pre></div>
<p>Khai báo biến sử dụng từ khóa <code>var</code>, nếu không gán giá trị khởi tạo
(initialization), các biến sẽ có <a href="https://golang.org/ref/spec#The_zero_value">zero
value</a> tương ứng với kiểu
của nó. Tức int sẽ = 0, bool = false, string = "", pointer = nil. Nếu có gán
giá trị, biểu thức theo sau sẽ được tính toán và biến sẽ chứa giá trị này.</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span> <span class="nx">y</span> <span class="kt">int</span> <span class="p">=</span> <span class="mi">1</span>
<span class="kd">var</span> <span class="nx">x</span> <span class="kt">int</span> <span class="p">=</span> <span class="nx">y</span> <span class="o">+</span> <span class="mi">1</span>
<span class="nb">print</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
<span class="c1">// Output</span>
<span class="c1">// 2</span>
</code></pre></div>
<p><em>Short variable declarations</em>, không cần ghi type, và dùng dấu <code>:</code></p>
<div class="highlight"><pre><span></span><code>ShortVarDecl = IdentifierList ":=" ExpressionList .
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="nx">s</span> <span class="o">:=</span> <span class="s">"Pymier"</span>
<span class="nx">x</span><span class="p">,</span> <span class="nx">y</span> <span class="o">:=</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">7</span>
<span class="nx">x</span><span class="p">,</span> <span class="nx">z</span> <span class="o">:=</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">9</span>
<span class="nb">print</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">z</span><span class="p">)</span>
<span class="c1">// Pymier679</span>
</code></pre></div>
<p>https://golang.org/ref/spec#Variable_declarations</p>
<h2>Scope</h2>
<p>Scope là phạm vi hoạt động của một biến.
Mỗi tên biến chỉ có thể được định nghĩa duy nhất 1 lần trong mỗi block
(đánh dấu bởi <code>{}</code>) như trong if/for/switch
hay function, biến này không thoát ra ngoài block - hay có scope trong block đó.</p>
<div class="highlight"><pre><span></span><code> <span class="kd">var</span> <span class="nx">x</span> <span class="kt">int</span> <span class="p">=</span> <span class="mi">3</span>
<span class="p">{</span>
<span class="kd">var</span> <span class="nx">x</span> <span class="kt">string</span> <span class="p">=</span> <span class="s">"Python"</span>
<span class="nb">println</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
<span class="p">}</span>
<span class="nb">println</span><span class="p">(</span><span class="nx">x</span><span class="p">)</span>
<span class="c1">//Python</span>
<span class="c1">//3</span>
</code></pre></div>
<p>điều này nghĩa là nếu khai báo 1 variable trong vòng <code>for</code> hay trong
điều kiện <code>if</code>, thì chúng không thoát ra ngoài khỏi các khối ấy, sẽ
không có hiện lượng <strong>leak</strong> variable như trong Python for:</p>
<div class="highlight"><pre><span></span><code><span class="n">p</span> <span class="o">=</span> <span class="s2">"Mi"</span>
<span class="n">s</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">3</span><span class="p">):</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span> <span class="o">+</span> <span class="n">p</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p</span><span class="p">)</span>
<span class="c1"># Kết quả hiện ra 2, không hiện ra Mi</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"</span><span class="si">{}</span><span class="s2"> is hoc sinh gioi"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">p</span><span class="p">))</span>
</code></pre></div>
<p><em>Tính năng</em> này dễ dẫn đến bug nếu ta chuyển code đi xung quanh, hay vô tình đặt tên giống nhau tại các nơi khác, trong cùng 1 function.</p>
<p>Trong Go:</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span> <span class="nx">p</span> <span class="kt">string</span> <span class="p">=</span> <span class="s">"Mi"</span>
<span class="nx">s</span> <span class="o">:=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="nx">p</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">p</span> <span class="p"><</span> <span class="mi">3</span><span class="p">;</span> <span class="nx">p</span><span class="o">++</span> <span class="p">{</span>
<span class="nx">s</span> <span class="p">=</span> <span class="nx">s</span> <span class="o">+</span> <span class="nx">p</span>
<span class="p">}</span>
<span class="nb">println</span><span class="p">(</span><span class="nx">s</span><span class="p">,</span> <span class="nx">p</span><span class="p">)</span>
<span class="c1">// Output</span>
<span class="c1">// 3 Mi</span>
</code></pre></div>
<p>https://golang.org/ref/spec#Declarations_and_scope</p>
<h2>Struct</h2>
<p>struct là khác biệt lớn đầu tiên với người code Python không dùng class (mà dùng dict).
Do trong Go không thể tạo 1 map chứa các value khác kiểu, không
có đoạn code tương tự với code Python sau đây nếu chỉ dùng map:</p>
<div class="highlight"><pre><span></span><code><span class="n">boy</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Pika"</span><span class="p">,</span> <span class="s2">"age"</span><span class="p">:</span> <span class="mi">18</span><span class="p">}</span>
<span class="n">students</span> <span class="o">=</span> <span class="p">{</span>
<span class="mi">20088888</span><span class="p">:</span> <span class="n">boy</span><span class="p">,</span>
<span class="mi">20089999</span><span class="p">:</span> <span class="p">{</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Doraemon"</span><span class="p">,</span> <span class="s2">"age"</span><span class="p">:</span> <span class="mi">20</span><span class="p">},</span>
<span class="p">}</span>
<span class="nb">print</span><span class="p">(</span><span class="n">students</span><span class="p">[</span><span class="mi">20088888</span><span class="p">][</span><span class="s2">"name"</span><span class="p">])</span>
<span class="c1"># Pika</span>
</code></pre></div>
<p>Vậy nên khi cần biểu diễn 1 object trong Go, cần tạo struct:</p>
<div class="highlight"><pre><span></span><code><span class="kd">type</span> <span class="nx">Student</span> <span class="kd">struct</span> <span class="p">{</span>
<span class="nx">name</span> <span class="kt">string</span>
<span class="nx">age</span> <span class="kt">int</span>
<span class="p">}</span>
<span class="nx">boy</span> <span class="o">:=</span> <span class="nx">Student</span><span class="p">{</span><span class="nx">name</span><span class="p">:</span> <span class="s">"Pika"</span><span class="p">,</span> <span class="nx">age</span><span class="p">:</span> <span class="mi">18</span><span class="p">}</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%v\n"</span><span class="p">,</span> <span class="nx">boy</span><span class="p">)</span>
<span class="c1">// {Pika 18}</span>
<span class="nx">students</span> <span class="o">:=</span> <span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="nx">Student</span><span class="p">{</span>
<span class="mi">20088888</span><span class="p">:</span> <span class="nx">boy</span><span class="p">,</span>
<span class="mi">20089999</span><span class="p">:</span> <span class="nx">Student</span><span class="p">{</span><span class="nx">name</span><span class="p">:</span> <span class="s">"Doraemon"</span><span class="p">,</span> <span class="nx">age</span><span class="p">:</span> <span class="mi">20</span><span class="p">},</span>
<span class="p">}</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="nx">students</span><span class="p">[</span><span class="mi">20088888</span><span class="p">].</span><span class="nx">name</span><span class="p">)</span>
<span class="c1">// Pika</span>
</code></pre></div>
<p>Và vì thế, map trong Go sẽ chỉ còn được dùng khi cần "ghép cặp" key-value, tối
ưu cho việc tìm kiếm theo key. BIG NOTE: Go không có kiểu set built-in.</p>
<p>Để viết code Python "đúng" nhất theo Go, hãy tạo class mỗi khi cần 1 object,
việc này hợp lý trên lý thuyết, nhưng trong thực hành, khi nhận được 1 JSON
string rồi <code>json.loads</code> lên, không mấy ai ngồi viết class cả.
Trong Go thì bắt buộc phải ngồi viết struct cho JSON đó, hoặc dùng thư viện
cung cấp sẵn struct tương ứng giá trị JSON nói trên nếu dịch vụ có Go client
SDK.</p>
<h2>Pointer</h2>
<p>Now is the famous POINTER!</p>
<p>pointer vốn làm rụng tóc hàng ngàn sinh viên đại học khi học C, pointer trong
Go không khác nhiều với pointer trong C, nhưng sẽ là thứ rất khác biệt với
người đến từ Python.</p>
<p>Code trước, nói sau:</p>
<div class="highlight"><pre><span></span><code><span class="nx">x</span> <span class="o">:=</span> <span class="mi">96</span>
<span class="nx">p</span> <span class="o">:=</span> <span class="o">&</span><span class="nx">x</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"Value of p: %v - Type of p: %T\n"</span><span class="p">,</span> <span class="nx">p</span><span class="p">,</span> <span class="nx">p</span><span class="p">)</span>
<span class="c1">// Value of p: 0xc000014110 - Type of p: *int</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">"0xff == 255: "</span><span class="p">,</span> <span class="mh">0xff</span> <span class="o">==</span> <span class="mi">255</span><span class="p">)</span>
<span class="c1">// 0xff == 255: true</span>
</code></pre></div>
<p><code>p</code> có kiểu <code>*int</code>, và point to <code>x</code> (trỏ tới <code>x</code>), dấu <code>*</code> ở đây là
ký hiệu của kiểu pointer, đứng trước kiểu mà nó trỏ tới.
<code>p</code> là một pointer trỏ tới <code>x</code>, <code>x</code> có kiểu <code>int</code> nên <code>p</code> có kiểu <code>*int</code>.
Còn giá trị của <code>p</code> là gì? <code>0xc000014110</code> là cách viết hệ hexadecimal (hệ 16),
thường được dùng tới khi viết địa chỉ bộ nhớ máy tính.</p>
<p>Trong Python cũng vậy, khi print 1 instance của class (object):</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">python3</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'print(object())'</span>
<span class="o"><</span><span class="nb">object</span> <span class="nb">object</span> <span class="n">at</span> <span class="mh">0x7f2025653c80</span><span class="o">></span>
</code></pre></div>
<p>Chú ý kết quả trên máy bạn sẽ khác, do mỗi lần chạy, Go sẽ cấp 1 memory address
khác nhau.</p>
<h3>Địa chỉ bộ nhớ (memory address)</h3>
<p>Còn có các tên gọi khác như memory location, address.</p>
<p>Trong máy tính, bộ nhớ (RAM) lưu giữ các giá trị của chương trình
đang chạy, các thanh RAM có nhiều "ô", và mỗi ô được đánh số
(trên thực tế phưc tạp hơn khi phân thành 2 loại physical address/logical
address nhưng không bàn tới ở đây).
Khi tạo ra 1 giá trị trong Go, giá trị đó được chứa trong 1 ô, sử dụng phép
toán <a href="https://golang.org/ref/spec#Address_operators"><code>&</code> (address operator)</a> sẽ
lấy được địa chỉ này là một số int > 0 biểu diễn ở dạng hex.</p>
<h3>Pointer là gì</h3>
<p>Một pointer là một memory address - chỉ có vậy.
Nói <code>p</code> là một pointer point đến <code>x</code> là cách nói thuật ngữ của câu
<code>p</code> là giá trị memory address của ô chứa <code>x</code>.</p>
<p>Trong Go, một pointer luôn phải trỏ tới 1 giá trị x nào đó, ngoại trừ pointer mới declare sẽ có giá trị <code>nil</code>.</p>
<p>Nếu 1 tấm biển có ghi địa chỉ của 1 quán ăn, thì tấm biển chính là pointer tới quán ăn đó.</p>
<p>Để lấy giá trị mà <code>p</code> trỏ tới - tức là <code>x</code>, dùng ký hiệu: <code>*p</code>.
Tránh nhầm lẫn dấu <code>*</code> ở đây với dấu <code>*</code> trong kiểu của pointer. Dấu <code>*</code> ở đây gọi là <code>pointer indirection</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nx">x</span> <span class="o">:=</span> <span class="mi">96</span>
<span class="nx">p</span> <span class="o">:=</span> <span class="o">&</span><span class="nx">x</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"Value of *p: %v\n"</span><span class="p">,</span> <span class="o">*</span><span class="nx">p</span><span class="p">)</span>
<span class="o">*</span><span class="nx">p</span> <span class="p">=</span> <span class="mi">99</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"Value of x: %v\n"</span><span class="p">,</span> <span class="nx">x</span><span class="p">)</span>
<span class="c1">// Value of *p: 96</span>
<span class="c1">// Value of x: 99</span>
</code></pre></div>
<p>Thay đổi <code>*p</code> chính là thay đổi <code>x</code>.</p>
<h3>Pointer dùng để làm gì?</h3>
<p>Vì pointer là memory address, tức một con số kiểu <code>int</code>, nó rất nhỏ, nhẹ. Người
ta dùng nó vì muốn "nhẹ", muốn tiết kiệm bộ nhớ (RAM).
Pointer thường được dùng để pass argument cho function hay return kết quả từ
function.</p>
<h4>Call by value là gì</h4>
<p>Go function <a href="https://golang.org/ref/spec#Calls">call by value</a>, tức nếu gọi function với argument nào, thì function được gọi sẽ nhận được 1 bản <strong>copy</strong> của argument đó. Khi function return, giá trị return này được <strong>copy</strong> tới function gọi.</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">n</span> <span class="o">:=</span> <span class="s">"meo Meo"</span>
<span class="nx">s</span> <span class="o">:=</span> <span class="nx">strings</span><span class="p">.</span><span class="nx">ToUpper</span><span class="p">(</span><span class="nx">n</span><span class="p">)</span>
<span class="o">...</span>
<span class="p">}</span>
</code></pre></div>
<p>Ví dụ trên, <code>main</code> gọi <code>ToUpper</code>, <code>ToUpper</code> sẽ nhận được 1 bản copy của <code>n</code>, khi <code>ToUpper</code> xử lý xong, <code>main</code> sẽ nhận được 1 bản copy của kết quả mà <code>ToUpper</code> return.</p>
<p>Copy không có vấn đề gì khi kích thước nhỏ, nhưng khi kích thước dữ liệu lớn, copy sẽ tốn RAM/CPU, vậy nên người ta dùng pointer để chia sẻ chung address, dùng chung 1 giá trị thay vì copy để tiết kiệm RAM.
Việc này thực hiện bằng cách gọi function với pointer argument và return kiểu pointer. Khi call function với pointer argument, pointer argument vẫn được copy, nhưng copy address chỉ là copy 1 số int nhỏ bé.</p>
<p>Note: <a href="https://pymi.vn/blog/call-by/">Python call by object reference</a></p>
<p>Dẫn tới câu hỏi tiếp theo...</p>
<h3>Bao nhiêu là lớn, khi nào thì dùng pointer?</h3>
<p>Một giá trị kiểu <code>int</code> với kích thước lớn nhất trong Go là <code>int64</code> có kích thước 64 bits hay 64/8 == 8 bytes, đây được coi là nhỏ. Vậy bao nhiêu là lớn? 80 bytes? 800 bytes? 8 Kilobyte (8000B, 8KB) ...? quyết định sao là ở lập trình viên, <a href="https://stackoverflow.com/questions/23542989/pointers-vs-values-in-parameters-and-return-values">và vì lập trình viên nhiều khi cũng không biết thế nào là lớn, nên hầu hết đều mặc định dùng pointer cho "tiết kiệm RAM"/nhẹ.</a></p>
<p>Nếu tác giả của Go cũng nghĩ vậy, có lẽ họ mặc định function call by reference (dùng chung thông qua chia sẻ địa chỉ) thay vì call by value (copy giá trị) luôn cho khỏi phải nghĩ.</p>
<p>Nhìn kỹ ra, lý do dùng pointer nói trên là 1 cách để "tối ưu" về bộ nhớ và tốc độ, nhưng việc này lại không tối ưu cho sự đơn giản, dễ hiểu của code - thứ luôn được đánh giá quan trọng hơn trong các chương trình thực tế.</p>
<p>Không bao giờ bạn thấy function <code>sump</code> nào trông thế này cả, kể cả nó
có vẻ như dùng ít tài nguyên hơn:</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span> <span class="nx">sump</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="o">*</span><span class="kt">int</span><span class="p">)</span> <span class="o">*</span><span class="kt">int</span> <span class="p">{</span>
<span class="nx">r</span> <span class="o">:=</span> <span class="p">(</span><span class="o">*</span><span class="nx">a</span> <span class="o">+</span> <span class="o">*</span><span class="nx">b</span><span class="p">)</span>
<span class="k">return</span> <span class="o">&</span><span class="nx">r</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="kt">int</span><span class="p">)</span> <span class="kt">int</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">a</span> <span class="o">+</span> <span class="nx">b</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">a</span><span class="p">,</span> <span class="nx">b</span> <span class="o">:=</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">2</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%d\n"</span><span class="p">,</span> <span class="o">*</span><span class="nx">sump</span><span class="p">(</span><span class="o">&</span><span class="nx">a</span><span class="p">,</span> <span class="o">&</span><span class="nx">b</span><span class="p">))</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%d\n"</span><span class="p">,</span> <span class="nx">sum</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">b</span><span class="p">))</span>
<span class="p">}</span>
<span class="c1">// 3</span>
<span class="c1">// 3</span>
</code></pre></div>
<p>Nếu tới đây vẫn chưa hiểu pointer là gì, hãy thử vận may với bài viết của <a href="https://dave.cheney.net/2017/04/26/understand-go-pointers-in-less-than-800-words-or-your-money-back">Dave Cheney</a></p>
<h2>Method</h2>
<p>method của struct là một cú pháp rất sáng tạo và khác biệt với các ngôn ngữ phổ biến (C, Python)... nhưng nó chỉ là 1 cách viết đơn giản (syntactic sugar) để pass struct argument cho method. Ví dụ sau đây, method addAge và
function addAge là như nhau.</p>
<div class="highlight"><pre><span></span><code><span class="kd">type</span> <span class="nx">User</span> <span class="kd">struct</span> <span class="p">{</span>
<span class="nx">name</span> <span class="kt">string</span>
<span class="nx">age</span> <span class="kt">int</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="p">(</span><span class="nx">u</span> <span class="o">*</span><span class="nx">User</span><span class="p">)</span> <span class="nx">addAge</span><span class="p">(</span><span class="nx">n</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">u</span><span class="p">.</span><span class="nx">age</span> <span class="o">+=</span> <span class="nx">n</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">addAge</span><span class="p">(</span><span class="nx">u</span> <span class="o">*</span><span class="nx">User</span><span class="p">,</span> <span class="nx">n</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">u</span><span class="p">.</span><span class="nx">age</span> <span class="o">+=</span> <span class="nx">n</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">u</span> <span class="o">:=</span> <span class="nx">User</span><span class="p">{</span><span class="nx">name</span><span class="p">:</span> <span class="s">"Pika"</span><span class="p">,</span> <span class="nx">age</span><span class="p">:</span> <span class="mi">18</span><span class="p">}</span>
<span class="nx">u</span><span class="p">.</span><span class="nx">addAge</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
<span class="nx">v</span> <span class="o">:=</span> <span class="nx">User</span><span class="p">{</span><span class="nx">name</span><span class="p">:</span> <span class="s">"Pika"</span><span class="p">,</span> <span class="nx">age</span><span class="p">:</span> <span class="mi">18</span><span class="p">}</span>
<span class="nx">addAge</span><span class="p">(</span><span class="o">&</span><span class="nx">v</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%v == %v\n"</span><span class="p">,</span> <span class="nx">u</span><span class="p">,</span> <span class="nx">v</span><span class="p">)</span>
<span class="p">}</span>
<span class="c1">// Kết quả như nhau</span>
</code></pre></div>
<p><code>struct</code> argument được mặc định pass vào cho method gọi là <code>method receiver</code>,
method receiver hầu hết đều là pointer, lý do xem lại phần pointer và link đính kèm.
<code>method receiver</code> giống như <code>self</code> argument trong method của Python class.</p>
<h2>Interface</h2>
<p>Một var kiểu <code>interface{}</code> có thể chứa mọi giá trị, biến Go thành dynamic
typing giống như Python, nhưng đấy không phải lý do người ta tạo ra interface.</p>
<p>Trích Go <a href="https://golang.org/ref/spec#Interface_types">spec</a>:</p>
<blockquote>
<p>An interface type specifies a method set called its interface. A variable of interface type can store a value of any type with a method set that is any superset of the interface. Such a type is said to implement the interface. The value of an uninitialized variable of interface type is nil.</p>
</blockquote>
<div class="highlight"><pre><span></span><code><span class="nx">InterfaceType</span> <span class="p">=</span> <span class="s">"interface"</span> <span class="s">"{"</span> <span class="p">{</span> <span class="p">(</span> <span class="nx">MethodSpec</span> <span class="p">|</span> <span class="nx">InterfaceTypeName</span> <span class="p">)</span> <span class="s">";"</span> <span class="p">}</span> <span class="s">"}"</span> <span class="p">.</span>
<span class="nx">MethodSpec</span> <span class="p">=</span> <span class="nx">MethodName</span> <span class="nx">Signature</span> <span class="p">.</span>
<span class="nx">MethodName</span> <span class="p">=</span> <span class="nx">identifier</span> <span class="p">.</span>
<span class="nx">InterfaceTypeName</span> <span class="p">=</span> <span class="nx">TypeName</span> <span class="p">.</span>
</code></pre></div>
<p>kiểu <code>interface</code> dùng để định nghĩa 1 bộ các method. Thay vì cách dùng trong OOP phổ biến (Java, C#), một giá trị kiểu A là một giá trị kiểu A hoặc kế thừa kiểu A (inheritance), thì Go nói một giá trị kiểu A nếu nó có đủ các method trong interface A.
Nhờ tính năng này, Go có thể viết 1 function nhận vào 1 interface, và khi gọi, có thể nhận vào bất kỳ kiểu nào đã implement interface này. Đây gọi là <code>polymorphism</code> - 1 trong <a href="https://www.freecodecamp.org/news/four-pillars-of-object-oriented-programming/">4 đặc tính quan trọng của OOP</a>.</p>
<h2>Packages</h2>
<p>Mỗi thư mục trong Go là 1 package riêng biệt, thư mục con là 1 package khác, không liên quan gì tới thư mục chứa nó.
<a href="https://docs.google.com/document/d/1e8kOo3r51b2BWtTs_1uADIA5djfXhPT36s6eHVRIvaU/edit#!">Thư mục <code>internal/</code> chỉ cho phép các thư mục chứa nó dùng, không cho phép các thư mục/project khác dùng</a>.</p>
<h2>Concurrency</h2>
<p>Concurrency <strike>trong Go</strike> là một vấn đề phức tạp.
Mặc dù concurrency là một thế mạnh của Go so với các ngôn ngữ khác,
nó vẫn đầy những vấn đề khó khăn, dễ sai, và không hoàn hảo (goroutine leak).
thread & multiprocess & async trong Python, hay các ngôn ngữ khác, cũng không phải ngoại lệ.</p>
<h2>Cost</h2>
<p>Một triết lý trong thiết kế của Go là khiến lập trình viên nhìn thấy
"cost" của code mình viết.
Ví dụ trong Python viết:</p>
<div class="highlight"><pre><span></span><code><span class="mi">1</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">6</span><span class="p">]</span> <span class="c1"># O(n)</span>
<span class="mi">1</span> <span class="ow">in</span> <span class="p">{</span><span class="mi">5</span><span class="p">:</span> <span class="s1">'a'</span><span class="p">,</span> <span class="mi">6</span><span class="p">:</span> <span class="s1">'b'</span><span class="p">}</span> <span class="c1"># O(1)</span>
</code></pre></div>
<p>chỉ thấy 1 chữ <code>in</code> và không thấy <strong>rõ</strong> là phải mất bao nhiêu phép tính, thì Go tối ưu thiết kế cho việc đó, dẫn tới việc muốn tìm 1 giá trị trong 1 slice/array, chỉ có cách là dùng 1 vòng for duyệt qua từng phần tử -> O(n).</p>
<h2>Stdlib for DevOps</h2>
<ul>
<li>Xử lý CLI argument: <code>flag</code></li>
<li>Logging: <code>log</code></li>
<li>Testing: <code>testing</code></li>
<li>Xử lý file, thư mục, process: <code>io</code>, <code>os</code></li>
<li>HTTP: <code>net/http</code></li>
<li>JSON: <code>encoding/json</code></li>
</ul>
<p>Cộng đồng Go <a href="https://go-proverbs.github.io/">ưa chuộng việc copy 1 chút thay vì cài thêm thư viện bên ngoài</a>, ưu chuộng dùng bộ stdlib có sẵn.</p>
<h3>Example</h3>
<p>Script liệt kê các GitHub repositories của 1 GitHub user (NOTE: no pagination)</p>
<div class="highlight"><pre><span></span><code><span class="cm">/*</span>
<span class="cm">Script lists GitHub repos of given username.</span>
<span class="cm">*/</span>
<span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="p">(</span>
<span class="s">"encoding/json"</span>
<span class="s">"flag"</span>
<span class="s">"fmt"</span>
<span class="s">"log"</span>
<span class="s">"net/http"</span>
<span class="s">"os"</span>
<span class="p">)</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">urlFmt</span> <span class="o">:=</span> <span class="s">"https://api.github.com/users/%s/repos"</span>
<span class="nx">flag</span><span class="p">.</span><span class="nx">Parse</span><span class="p">()</span>
<span class="nx">username</span> <span class="o">:=</span> <span class="nx">flag</span><span class="p">.</span><span class="nx">Arg</span><span class="p">(</span><span class="mi">0</span><span class="p">)</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="nx">username</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"Usage: ./cli username\n"</span><span class="p">)</span>
<span class="nx">os</span><span class="p">.</span><span class="nx">Exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">url</span> <span class="o">:=</span> <span class="nx">fmt</span><span class="p">.</span><span class="nx">Sprintf</span><span class="p">(</span><span class="nx">urlFmt</span><span class="p">,</span> <span class="nx">username</span><span class="p">)</span>
<span class="nx">resp</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">http</span><span class="p">.</span><span class="nx">Get</span><span class="p">(</span><span class="nx">url</span><span class="p">)</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"Failed to get URL: %s"</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">decoder</span> <span class="o">:=</span> <span class="nx">json</span><span class="p">.</span><span class="nx">NewDecoder</span><span class="p">(</span><span class="nx">resp</span><span class="p">.</span><span class="nx">Body</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">repos</span> <span class="p">[]</span><span class="kd">struct</span> <span class="p">{</span>
<span class="nx">Name</span> <span class="kt">string</span>
<span class="nx">HtmlUrl</span> <span class="kt">string</span> <span class="s">`json:"html_url"`</span>
<span class="p">}</span>
<span class="nx">err</span> <span class="p">=</span> <span class="nx">decoder</span><span class="p">.</span><span class="nx">Decode</span><span class="p">(</span><span class="o">&</span><span class="nx">repos</span><span class="p">)</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">"Failed to decode JSON: %s"</span><span class="p">,</span> <span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">for</span> <span class="nx">idx</span><span class="p">,</span> <span class="nx">i</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">repos</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%3d %s %s\n"</span><span class="p">,</span> <span class="nx">idx</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="nx">i</span><span class="p">.</span><span class="nx">Name</span><span class="p">,</span> <span class="nx">i</span><span class="p">.</span><span class="nx">HtmlUrl</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<h2>Go đơn giản, nhưng không dễ</h2>
<p>Go đơn giản - đơn giản tức là ít khái niệm, ít cú pháp. Nhưng không dễ - dễ tức là không mất nhiều suy nghĩ, nhiều công sức làm một việc. Kiểm tra 1 phần tử trong slice, thêm 1 phần tử vào giữa array, ... các "bài toán nhỏ" mà Python chỉ mất 1 dòng code, thì trong Go là cả một vấn đề lớn mà người ta phải viết hẳn <a href="https://github.com/golang/go/wiki/SliceTricks">wiki rồi chia sẻ</a>.</p>
<p><a href="https://www.arp242.net/go-easy.html">Go is simple, but not easy</a>.</p>
<p>Go có không it trap/gotchas dù ngôn ngữ có vẻ đơn giản:</p>
<ul>
<li><a href="http://devs.cloudimmunity.com/gotchas-and-common-mistakes-in-go-golang/">50 Shades of Go: Traps, Gotchas, and Common Mistakes for New Golang Devs</a></li>
<li><a href="https://rytisbiel.com/2021/03/06/darker-corners-of-go/">Darker Corners of Go</a></li>
</ul>
<h2>Like & Dislike</h2>
<p>Vậy anh có thích Go hay không?</p>
<p>Đó là 1 câu hỏi bẫy! ngày còn 20, tôi sẽ trả lời ngay là không hay có, nhưng giờ nhận ra, cuộc sống đâu phải là binary mà chỉ có 0 với 1. Khi ta thích thứ gì, ta có thể thích chỗ này không thích chỗ kia. Khẩu vị con người cũng thay đổi, thứ không thích hôm nay có khi mai lại "từ thích thích thành thương thương"... Ngôn ngữ lập trình cũng chỉ là công cụ, có gì mà phải yêu với ghét...</p>
<p><a href="https://youtu.be/OWQMcEa6fr4"><img alt="Amee" src="https://img.youtube.com/vi/OWQMcEa6fr4/0.jpg"></a></p>
<h3>Like</h3>
<ul>
<li>Compile thành binary + cross compiling: deploy trở thành câu chuyện rất đơn giản: copy file.</li>
<li>Bộ stdlib đầy đủ: HTTP, JSON - hai công cụ quan trọng của DevOps.</li>
<li>Được hỗ trợ rộng rãi bởi các nhà cung cấp dịch vụ: AWS, GCP... tất nhiên Python cũng được hỗ trợ nhiều, nhưng nếu bạn dùng 1 ngôn ngữ trẻ nào đó như... Elixir, <a href="https://blog.darklang.com/new-backend-fsharp/">Rust hay già mà không rất phổ biến như Erlang, Ocaml thì đều không được hỗ trợ nhiều vậy</a>.</li>
</ul>
<h3>Dislike</h3>
<ul>
<li>Code dài dòng</li>
<li>Không easy</li>
<li>Quản lý package thay đổi liên tục từ 2014, giờ chắc đã ổn.</li>
</ul>
<h2>Kết luận</h2>
<p>Go là một ngôn ngữ đơn giản nhưng không dễ, ai cũng học được nhưng để có thể
dùng thoải mái sẽ cần một thời gian luyện tập cho quen sau khi đã dính lời
nguyền ngắn gọn, dễ dàng của Python. Go là một ngôn ngữ hiện đại, tốt, đáng bỏ
thời gian đầu tư làm quen để cho vào bộ công cụ của bạn... và vẫn yêu Python.</p>
<h2>References</h2>
<ul>
<li><a href="https://golang.org/ref/spec">Go spec</a></li>
<li><a href="https://go-proverbs.github.io/">go-proverbs</a></li>
<li><a href="https://blog.darklang.com/new-backend-fsharp/">Ocaml to F#</a></li>
<li><a href="https://blog.darklang.com/new-backend-fsharp/">Dave Cheney on pointer</a></li>
<li><a href="https://www.arp242.net/go-easy.html">Go easy</a></li>
<li><a href="https://github.com/ardanlabs/gotraining">Ultimate Go - non-free course</a></li>
<li><a href="https://www.infoq.com/presentations/Simple-Made-Easy/">Simple made easy</a></li>
</ul>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>
sau một lần nữa 6 tháng code Go và chắc lại còn lâu mới code nữa...</p>Mẹ ơi, con trượt rồi!2020-10-05T00:00:00+07:002020-10-05T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-10-05:/article/dh/<p>Trượt đại học, rồi sao? không học đại học, thì làm cái gì?</p><p>Tôi là một người may mắn, và tôi không phải nói câu này.
Nhưng giờ đã 12 năm sau cái ngày đó (2008), nhìn lại, thì đại học mang lại
cho tôi những gì?
Và nếu bạn trượt đại học, thì giờ làm gì đây?</p>
<h2>Trượt đại học thì sao?</h2>
<p>Thì buồn.</p>
<p>Đấy là điều đầu tiên có thể cảm nhận được. Dù chưa trải qua, chỉ cần nhìn những
đứa bạn kém may mắn hơn chúng nó buồn, thậm chí còn xa lánh mình, sẽ hiểu
trượt đại học thì buồn.</p>
<h3>Thế tại sao lại buồn?</h3>
<p>Xã hội sinh ra một định kiến: học hết cấp 3 thì PHẢI thi đại học, và đỗ
đại học là giỏi, là vinh quang, còn trượt thì là ... không giỏi.
Bạn sẽ bị coi như 1 kẻ kém cỏi, hàng xóm, người quen, bạn bố mẹ sẽ hỏi thăm.
Và bởi vì nó đã là một định kiến xã hội, nên 1 2 con người thay đổi cũng
gặp rất nhiều khó khăn.
Cái sức ép xã hội khiến bạn phải buồn, khiến bạn nghĩ rằng mình kém cỏi, vậy nên
bạn buồn. Hay đơn giản buồn vì bố mẹ/người thân bạn buồn.
Ngoài ra, phải rời xa bạn bè vì chúng nó đều đi lên thành phố học hết, cũng buồn.</p>
<p>Nhớ rằng "học tài, thi phận", bởi nếu như bạn được 25 điểm mà cái trường bạn
chọn năm nay nó tăng nhanh như chỉ số PM 2.5 ở thủ đô, lấy hản 27 thay vì 23
như năm trước, thì đâu phải do bạn kém?
Hay thằng hàng xóm được 15 điểm, đăng ký vào trường năm nay lấy 15, thì nó
lại đỗ. Hay con ông kia ôn trúng tủ, còn bạn thì lệch, nó đỗ là may hơn, chứ
đâu phải giỏi hơn.</p>
<p>Năm 18 tuổi, tôi thi đại học vì 1 lý do đơn giản: vì đấy là điều
ai cũng làm sau khi học xong lớp 12. Trong cái đầu của 1 thằng học sinh miền núi
đi thi được cộng thêm 1.5 điểm, không hề từng nghĩ đến câu "tại sao lại phải học đại học",
hay "học đại học để làm gì?". Chuyện chọn trường cũng đơn giản chỉ là "thích nghịch
máy tính thì vào bách khoa" chứ chả hiểu bách khoa dạy cái gì để làm với máy tính.</p>
<p>Thế nên bạn có thể buồn, nhưng nhớ nghĩ kỹ hơn chút, là tại sao mình phải buồn.</p>
<h2>Học đại học để làm gì</h2>
<p>Ngày ấy, hiểu đơn giản là cứ đi đại học thôi, đi đại học là thành công,
đi đại học để học đại học.
Học để ra trường, kiếm được 1 tấm <strong>bằng đại học</strong>, rồi cầm về để mang đi xin
việc, làm ở cơ quan nào đó.</p>
<p>Giờ ngẫm lại, xã hội muốn mình đi học đại học mục đích ra là để kiếm
cho mình được 1 công việc, nuôi bản thân, đóng góp cho xã hội.</p>
<p>Cái bằng là thứ công cụ để xin việc của thời ngày xưa, là tấm vé giải độc đắc
để kiếm được 1 chỗ ngon trong cơ quan toàn là cán bộ.</p>
<p>Còn giờ là năm thứ 20 của thế kỷ 21, tấm bằng trở nên bớt quan trọng hơn nhiều,
kể cả cầm 1 tấm bằng, đi xin việc thì vẫn phải THI (hay gọi nhẹ nhàng hơn là <a href="http://pp.pymi.vn/article/phongvan/">"phỏng vấn"</a>).
Ngoài ra, rất nhiều nơi / ngành nghề, không hề quan tâm đến bằng cấp, đặc biệt
là ngành công nghệ thông tin.</p>
<p>Nếu anh giỏi, anh làm được, thì mang cái sản phẩm của mình ra mà khoe, còn
anh chả có gì khoe, thì đành thôi: mang khoe cái bằng.</p>
<ul>
<li>Xin chào, đây là website tôi làm ra, giải quyết vấn đề này, sử dụng công nghệ
kia ... app di động 1 triệu lượt tải trên app store ...</li>
<li>Xin chào, em không biết code web/app nhưng em có bằng khá Bách Khoa, em chưa
làm ra sản phẩm nào ngoài đoạn code tính tổng từ 1 đến 1000.</li>
</ul>
<p>Cái bằng, không phải là mục đích của việc học đại học. Học đại học là để có được
"kỹ năng"/"trình độ", cái bằng chỉ là "bằng chứng" do trường đại học đó đánh giá,
theo tiêu chuẩn của trường đó.
Vậy nên nếu không học đại học, ta sẽ không có "bằng chứng" do trường đại học đó
cung cấp, nhưng không hề liên quan đến chuyện "kỹ năng"/"trình độ", thứ mà
hoàn toàn có thể học ở chỗ khác.</p>
<h2>Học đại học được gì</h2>
<p>Nếu coi trường học như một cỗ máy, thì đầu vào là các sinh viên khá "thông minh"
do điểm thi đầu vào trên mức N, còn đầu ra sẽ các kỹ sữ biết làm việc mà họ
được đào tạo. Lý thuyết là vậy, còn thực tế thì khác hoàn toàn lý thuyết.</p>
<p><img alt="Khuôn mặt một sinh viên mùa ôn thi" src="http://pp.pymi.vn/images/ray-hennessy-2NwntN4J2Gw-unsplash.jpg">
<span>Khuôn mặt một sinh viên mùa ôn thi - Photo by <a href="https://unsplash.com/@rayhennessy?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Ray Hennessy</a> on <a href="https://unsplash.com/t/nature?utm_source=unsplash&utm_medium=referral&utm_content=creditCopyText">Unsplash</a></span></p>
<p>Nhìn lại, 5 năm đại học tôi có được:</p>
<ul>
<li>Được lên thủ đô sống, làm quen với "văn minh xã hội". Lần đầu xuống nhập học
được học ngay bài học ăn phở không hỏi giá, bị chém đẹp 50k (thời 2008).</li>
<li>Những người bạn. Vui vẻ cùng nhau đến lớp ngủ, cùng nhau vượt qua các kỳ thi
lại, cùng nhau uống rượu, cùng nhau đi chơi...
Thứ tình bạn trong sáng (như hồi cấp 3), những người anh em chia ngọt sẻ bùi,
điều mà sau này đi làm rồi, hay có gia đình riêng rồi, sẽ rất khó mà kiếm được.</li>
<li>Quãng thời gian sinh viên, học hành thì ít mà chơi thì nhiều, những va vấp
nhẹ nhàng với xã hội khi lần đầu rời xa gia định đến 1 nơi xa lạ.</li>
<li>Một ngôi trường có cháo lòng, cháo sườn, bánh mì, rượu ốc ngon.</li>
<li>Những hoạt động thanh niên cấp tiến như sinh viên tình nguyện, tiếp sức mùa
thi, chiếc khăn gió ấm, mùa đông không lạnh...</li>
<li>Khả năng tự học: cái này là kỹ năng tự luyện được, lôi sách lôi vở ra mà
đọc mà luyện đề thi còn qua môn, chứ làm gì có ai dạy cho.</li>
</ul>
<p>Còn kiến thức? không nhiều lắm. Những kiến thức ngày nay đi làm, đều do tôi
tự mày mò, nhờ sự ham mê máy tính từ ngày còn nhỏ.
Blog <a href="https://www.familug.org/">FAMILUG</a> viết từ năm thứ 2 đại học là bằng chứng
ấy. Tôi code Python khi ở Việt Nam chẳng mấy ai biết tên, tôi dùng Ubuntu
khi mọi người còn không biết nó tồn tại.</p>
<p>Nói vậy không có nghĩa là 5 năm trường không dạy gì. Trường có dạy,
vấn đề là bạn có học hay không? và học có giỏi hay không? Một phần nhỏ sinh viên
thuộc nhóm có học, và học giỏi, ra sẽ học lên cao, xin học bổng nước ngoài,
được giữ làm giảng viên, hay xin được công việc phù hợp. Nhưng nhớ là số này
rất ít, một khóa học 100 sinh
viên thì có 1, 2, 3, 4 thằng, chứ không phải là 20 thằng. Với nhóm này, học đại học
còn có thêm cái lợi: quan hệ tốt với thầy cô, nhiều cơ hội xin học bổng du học
lên cao, làm đề tài nghiên cứu khoa học cùng thầy cô ...
Đây là cái đầu ra mong ước, chuẩn của các trường. Số còn lại, chiếm tới hơn 90%,
mới là phần đáng nói của câu chuyện.</p>
<p>Bằng giỏi Bách Khoa thời ấy, hiếm như khẩu trang đầu mùa COVID-19, sinh viên
ra trường đúng hạn thì ít, bằng khá đã là học hành tử tế lắm rồi. Bằng khá Bách
Khoa nó không nhiều nhan nhản như bằng giỏi bên kinh tế nha. Số này cỡ
2 chục người trong 1 khóa 100 người, cũng bằng số sinh viên ra trường trước hạn,
bỏ học/đuổi học/quên đăng ký môn học... rồi bị đuổi.</p>
<p>Cái nhóm 60 sinh viên bằng "trung bình", ra trường không đúng hạn ấy, học được gì
ở trường? Năm 2008-2013+, khoa/viện toán ứng dụng và tin học Bách Khoa Hà Nội dạy:</p>
<ul>
<li>2 năm học đại cương: toán, lý, hóa, nhiệt, điện, cơ học kỹ thuật, vẽ kỹ
thuật, điện tử, triết học Mac Lê Nin, tư tưởng Hồ Chí Minh, ... các môn học
được coi là nền tảng phải có của mọi kỹ sư. Và để thêm chắc, các môn toán lý hóa
có tới 5 7 môn khác nhau, lý 1 2 3 nữa cho nó chắc. Đây là thời kỳ học chán nản
nhất cho các sinh viên, trượt/thi lại triền miên, bởi đây toàn những môn lý thuyết
khó nhai. Có những môn học lại 10 kỳ, cho đến khi qua được thì cũng là lúc ra trường.
Gọi là học, chứ thi xong là coi như trả hết chữ thầy, kiến thức cũng chẳng dùng
đến bao giờ, như cách tra biểu đồ nhiệt chẳng hạn. Ở đâu đó, ai đó làm một
nghề nào đó, có thể đang dùng, thậm chí là quan trọng. Sinh viên ra trường đi
làm có người dùng tới có người không, nên nhớ đây là chương trình để đào tạo
hàng loạt người, nên đứng dưới góc độ nhà trường, bạn thà dạy thừa còn hơn thiếu.</li>
<li>3 năm học chuyên ngành: Toán-Tin. Hầu hết sinh viên khóa này học toán-tin đơn
giản là vì trượt nguyện vọng 1, rơi tự do xuống mà vào toán tin. Bách Khoa thời
ấy, học 1 năm đại cương, lấy điểm 2 kỳ đầu để xét phân khoa. Đây cũng là thời mà hận những thằng giỏi học đại cương, chúng nó nộp vào CNTT
ầm ầm, chỉ đơn giản là vì ngành hot, ra dễ kiếm việc lương cao - chứ thậm chí
chưa chạm tay vào máy tính 1 lần.
Khoa nào nhiều người vào thì hot, điểm cao, khoa nào không ai đăng ký... thì
rơi tự do.
Không nhiều sinh viên vào Toán-Tin để học toán, chắc cũng là một phần lý do
mà kết quả học tập cũng không mấy đẹp đẽ gì. Hơn nửa chương trình chuyên ngành
3 năm này là toán, một chút ít "tin" cho có gọi là. Sinh viên muốn làm được
đồ án tin để ra trường, phải tự kiếm chỗ mà học lập trình cho tử tế (tự học mà
giỏi được thì tốt, ai cũng làm được thì người ta không phải mở trường,
dựng trung tâm). Đừng hy vọng sẽ được học lập trình, ra thành 1 lập trình viên
chuyên nghiệp từ
đây. Với những sinh viên muốn theo ngành "tin học", thì phần toán kia rất khó
nuốt, khô khan, chẳng có chỗ dùng, không có hứng học, thi khó qua, điểm sẽ thấp.
Chương trình học lạc hậu, đặc biệt với các môn tin, khi mà công nghệ thay đổi
hàng năm còn toán thì trăm năm mới đổi. Đây là tên vài môn toán kinh (khủng)
điển: giải tích hàm, phương trình đạo hàm riêng,
giải tích số, giải tích phức, toán kinh tế, các phương pháp tối ưu, hệ mờ ?!?
Chất lượng giảng dạy thì rất phụ thuộc vào giảng viên, một số môn có thầy cô
dạy hay, phong cách hay, lôi cuốn sinh viên học dù môn học không có nhiều tính
ứng dụng thấy ngay, có môn thầy đến lớp hì hục chép đầy 6 cái bảng 9 lần rồi về,
sinh viên ra vào thậm chí không biết...</li>
</ul>
<p>Đấy là thời đó, cũng gần 10 năm rồi, thời ấy học mạng neuron và hệ mờ trên giấy,
chỉ biết lấy công thức ra tính, chả ai biết để làm gì,
chưa ai nghe tới Machine Learning hay Deep Learning.
Còn giờ thì <a href="https://vnexpress.net/dai-hoc-bach-khoa-ha-noi-lay-diem-chuan-cao-nhat-la-29-04-4171374.html">Toán Tin đầu vào hẳn 27.56</a>, xin được mừng cho viện nhà.</p>
<p>Xem <a href="http://sami.hust.edu.vn/co-van-hoc-tap/ke-hoach-hoc-tap-chuan/">chương trình đào tạo mới tại đây</a>
thấy đã bớt được nhiều môn học <strong>nền tảng</strong>, <strong>quan trọng</strong> như hóa hay nhiệt
đi rồi, mừng cho các tài năng đất nước.</p>
<p>Nói vậy, không nghĩa là không nên học đại học. Đỗ rồi thì đi học thôi,
nhưng nên thử để biết mình thích gì/hợp làm gì, mà tìm học thêm, chứ đừng
thụ động ngồi chờ trường dạy cho.</p>
<h2>Trượt đại học thì làm gì</h2>
<p>Trượt đại học thì đi học cái khác, hoặc đi làm. Học đại học là con đường có vẻ
như "dễ"/"sáng" nhất để dẫn đến "thành công"/"thành tài", nhưng không phải con
đường duy nhất. Cũng chẳng có gì đảm bảo, hàng năm <a href="https://vnexpress.net/topic/thac-si-that-nghiep-17483">sinh viên đại học, thậm chí
cao học, ra trường vẫn thất nghiệp ầm ầm</a></p>
<p>Nếu muốn, nghỉ ngơi 1 thời gian rồi 3 tháng sau bắt đầu ôn thi để sang năm...
chơi lại trò chơi may rủi.</p>
<p>Học đại học thời nay là một khoản đầu tư không nhỏ, học phí các trường công
giờ cũng cao chót vót, mỗi kỳ học đóng học phí bằng 1/2 -> 1 con iPhone mới
nhất vừa ra. Học 10 kỳ là 10 con (~tối thiểu 60 triệu cho 5 năm học), chưa kể
thi lại, chưa tính chi phí ăn ở 4-5 năm.
Nên nếu chỗ nào học phí 50 triệu, trong vòng 2 năm, hay <a href="https://pymi.vn/">5 triệu trong vòng 2
tháng</a>
mà cho bạn cơ hội kiếm việc làm lương 8-10 triệu/tháng thì đừng xem là đắt,
đi làm vài tháng là hồi vốn.
Ngoài ra, nếu tôi học nửa năm rồi đi làm, sau 5 năm tôi có 4.5 năm kinh nghiệm,
lương thừa X3 mấy cậu sinh viên mới rón rén ra trường.</p>
<p>Điều quan trọng là tìm ra thứ khiến mình thích thú, đam mê. Nếu có rồi, cứ thế
mà học theo, nếu chưa biết, đỗ đại học, đi học 5 năm chăm chỉ ra rồi nhận ra mình không
thích mới đổi, lúc ấy mới là muộn, đó mới là thất bại đáng lo.</p>
<p>Những thứ quan trọng cần đầu tư:</p>
<ul>
<li>Ngoại ngữ: phổ biến, dễ học nhất là tiếng Anh. Dù học đại học hay không, đây
vẫn là thứ quan trọng nhất. Nó mở ra cả 1 chân trời cơ hội, hàng triệu tài
liệu hướng dẫn miễn phí bằng tiếng Anh trên internet, dù học bất cứ thứ gì,
điều kiện làm các công việc lương cao, công ty nước ngoài.</li>
<li>Rèn luyện sức khỏe: tập gì cũng được, có sức khỏe, có cơ thể đẹp
là một lợi thế cả đời, cực lớn, thậm chí giúp kiếm được nhiều công việc lương
cực cao, ngàn đô 1 ngày!</li>
</ul>
<p>Nếu muốn học ngành Công Nghệ Thông Tin, học xong đi kiếm việc làm lương khởi điểm
8-10 triệu, ghé ngay <a href="https://pymi.vn">PyMi.vn</a> để đăng ký học lập trình Python
tại Hà Nội - Sài Gòn (TP HCM).</p>
<h3>Học có 1.5 tháng mà đi làm được?</h3>
<p>Tất nhiên là phải học, phải đầu tư thời gian, chăm chỉ luyện bài, thì học ra mới
dễ xin được việc, mỗi tuần dành ~ 20 tiếng để cày cuốc bài tập.
Chứ đừng nghĩ tới lớp xem giảng bài, về nhà bận đi chơi, rồi
ra thì có người mời đi làm, sorry babe, ở đây không bán rượu mơ.</p>
<p>PyMi không chỉ dạy Python, mà sử dụng các công cụ / practice của lập trình viên
trong suốt quá trình học (vd: Git/GitLab/CI/Code Review).</p>
<h3>Lương chỉ có 8-10 triệu?</h3>
<p>Đây là mức lương khởi điểm cho vị trí lập trình viên Python (thường là lập
trình web-backend). Mức lương này ngang ngửa với sinh viên đại học mới ra trường.
Còn muốn học 2 tháng ra làm lương ngàn đô, có lẽ qua bên các kênh đầu tư tài
chính thì nhiều cơ hội hơn.</p>
<p>Sau khi đi làm 1 2 năm có thể tăng lên 15 triệu, hay $1000, và cứ thế tăng tiếp...
PS: muốn > $1000, phải có ngoại ngữ tốt.</p>
<h3>Tài liệu trên mạng đầy, lên youtube xem rồi tự học</h3>
<p>Tự học là một kỹ năng không phải ai cũng có.
Kỹ năng tự học phải luyện nhiều, mới có. Cũng chẳng phải đến đại học thì mới biết
tự học. Cứ tự học nhiều (như 5 năm ở Bách Khoa) thì khác dần biết cách thôi.
Nếu tự học mà hiệu quả được, đó là điều rất đáng mừng, bạn cũng không cần
phải đi đại học làm gì cho mất thời gian.</p>
<ul>
<li>Lên youtube xem/đọc tài liệu không hiểu thì lấy ai để hỏi?</li>
<li>Tài liệu chất lượng kém, đặc biệt các tài liệu tiếng Việt dùng Google
Translate rồi paste vào. Tài liệu viết bởi những người không có trình độ, liệu
bạn có phân biệt được?</li>
<li>Lên các diễn đàn/FaceBook group để hỏi rồi ngồi chờ hy vọng có ai đó tốt giúp
trả lời, mà trả lời đúng hay sai cũng không chắc nữa.</li>
</ul>
<p>Nếu lý do ngồi chờ cộng đồng trả lời sau vài ngày rồi mới học tiếp
là để "tiết kiệm tiền", thì chỉ chứng minh 1 điều là thời gian của bạn
quá rẻ.</p>
<p>Tự học ngoài ra còn gặp một số vẫn đề như: "đâm đầu vào tường" nhiều, do không
ai chỉ cho đâu là đúng sai, phải tự tìm ra, vậy nên mất thời gian hơn. Đi học,
không hỏi thầy thì còn hỏi bạn, nhanh hơn rất nhiều.
Ngoài ra bạn còn không biết mình không biết những gì.</p>
<p>Một khi đã tự học rồi, thì lo tập trung vào việc học, làm bài tập,
tránh mất thời gian ít bổ ích trên các group online.</p>
<h3>Các khóa học tiếng Anh trên mạng rẻ bèo, như Udemy</h3>
<p>Nếu xem được mà hiểu thì trình độ tiếng Anh của bạn cũng thuộc top trên của
người Việt Nam đó, chúc mừng nha. Như đã viết ở trên, nếu đã có tiếng Anh,
thì toàn bộ phần còn lại chỉ là game dễ.</p>
<p>Các khóa học cũng có loại this, loại that. Học code mà chỉ xem video, không thò
tay vào code, không có bài tập, không có sửa bài, thì chỉ là xem video.</p>
<p>Code là kỹ năng, muốn giỏi phải luyện tập nhiều.
Làm ra phải được chấm (review), để biết là tốt hay xấu, đúng hay sai.</p>
<h3>Học đại học 5 năm ra còn chẳng ăn ai, đòi học vài tháng</h3>
<p>Bạn đã thấy có thằng học như điên 3 năm, học đủ cả sinh sử địa, giáo dục công
dân nữa để "rèn luyện tư duy" mà trượt đại học khối A, còn có thằng đi ôn lò
vài tháng, trúng tủ đỗ luôn chưa?</p>
<p>5 năm đại học không phải là học cả 5 năm, thời gian chơi có khi hết 4 năm rưỡi.
10 thằng học giỏi có đến 90 thằng loại bình thường.</p>
<p>Xem lại chương trình đại học Bách Khoa ở trên để thấy nếu muốn làm lập trình
viên (nghề ghi đầu tiên trong bảng quảng cáo học Toán Tin), thì chỉ có vài môn
học cuối khóa có chút liên quan, mà muốn ra đi làm được vẫn phải học thêm rất
nhiều.</p>
<p>Học lâu mà sai không có ý nghĩa gì so với học đúng mà nhanh.</p>
<p>Kiến thức ở các trường đại học Việt Nam đều rất chậm thay đổi, trong khi công
nghệ thay đổi hàng năm. 5 năm học, dùng C/Pascal vài ba project nhỏ ở trường
sẽ khống kiếm cho bạn 1 công việc dev Python.</p>
<p>Giờ không biết đã trường nào dạy dùng git chưa, hay vẫn Turbo C màn hình xanh
menu đỏ rồi tự hào công nghệ?</p>
<h3>Học vậy không có "kiến thức cơ bản"/"kiến thức nền tảng"</h3>
<p>"Kiến thức cơ bản" cụ thể là cái gì? hiểu cách các vi mạch điện hoạt động
rồi chạy code như thế nào? (học đại học ở Việt Nam cũng không giúp bạn hiểu cái
này), hay hiểu cách đổi 8 bit thành 1 byte?</p>
<p>Hay kiến thức cơ bản là môn "cấu trúc dữ liệu giải thuật/toán rời rạc"
học trên giấy trong 1 kỳ mà cứ ngỡ như không đi đại học thì không được học/
không học được? Có bao nhiêu kiến thức cơ bản ấy ở lại trong sinh viên sau
5 năm ra trường, học môn ấy từ năm thứ 2.</p>
<p>Có 2 cách tiếp cần vấn đề, từ dưới lên, hoặc từ trên xuống. Các trường đại học
dạy theo kiểu từ dưới lên, đến lúc lên đến trên thì sinh viên đã quên hết cái
bên dưới.</p>
<p>Các trung tâm thì sẽ dạy từ trên xuống, học cái làm được luôn, có gì không hiểu
thì đào sâu, tìm hiểu thêm.</p>
<p>Đừng nghĩ biết code mấy cái thuật toán sắp xếp học ở trường mà hơn người ta
gõ <code>list.sort()</code>, cái bạn học ở trường, lúc đi ra làm, đâu có dùng?
Bạn có biết <code>list.sort()</code> của Python dùng thuật toán gì không?</p>
<p>Nếu bạn thuộc "on the top của hành tinh này", thiết kế thuật toán sắp xếp phù
hợp với bài toán cụ thể, rõ ràng bạn không phải đối tượng của bài viết này.</p>
<h3>Sao bài này toàn ví dụ viện Toán Tin - Bách Khoa Hà Nội</h3>
<p>Vì tác giả đã học ở đó. Bạn học khoa khác, cậu chuyện có thể khác.</p>
<h3>Học xong có làm được cái này, cái kia không?</h3>
<p>Vui lòng <strong>đọc kỹ</strong> trang chủ <a href="https://pymi.vn">Pymi</a> và CV mẫu của học viên
(cũng có link trên trang chủ).</p>
<p>Nói tóm lại, người muốn, sẽ tìm cách, còn không muốn, sẽ tìm lý do.</p>
<h2>Hành động của chúng ta (Action Item)</h2>
<ul>
<li>Tự hỏi xem học đại học để làm gì?</li>
<li>Ngừng huyễn hoặc bản về sự cao siêu của đại học, hay "chỉ học đại học mới được học".</li>
<li>Hồi tưởng xem học đại học đã học được gì có ích? và viết 1 bài.</li>
<li>Tìm cái bằng đại học xem nó ở góc nào.</li>
</ul>
<h2>Kết luận</h2>
<div class="highlight"><pre><span></span><code>"Don't let schooling interfere with your education." - Mark Twain
</code></pre></div>
<p>Trượt đại học nó không khủng khiếp như trượt vỏ chuối. Có đau vì sức ép xã hội,
chứ không lo đập đầu xuống đất mà mất trí.</p>
<p>Đứng dậy, cầm laptop lên và đi học Python tại <a href="https://pymi.vn">PyMi.vn</a> đi nhé.</p>
<h2>Đọc thêm</h2>
<ul>
<li><a href="http://pp.pymi.vn/article/phongvan/">"phỏng vấn"</a></li>
<li><a href="https://pymi.vn">Trang chủ PyMi.vn</a></li>
</ul>Vừa đủ để đi (go)2020-09-06T00:00:00+07:002020-09-06T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-09-06:/article/go/<p>Học vừa đủ Golang để nguy hiểm - phần 1</p><p>Series bài viết giúp lập trình viên đã biết Python bắt đầu code Go mượt mà hơn - tài liệu bổ trợ cho <a href="https://tour.golang.org/">Go tour</a> chứ không để thay thế - phần 1.</p>
<p>Đã có phần 2 <a href="http://pp.pymi.vn/article/go12/"><strong>Cứ đi là đến</strong></a>.</p>
<h2>Golang là gì</h2>
<p><img alt="gopher" src="https://golang.org/lib/godoc/images/home-gopher.png"></p>
<p>Go (hay còn gọi là Golang theo tên trang chủ <a href="https://golang.org">golang.org</a>) là một ngôn ngữ lập
trình mới xuất hiện trong công chúng vào năm 2009 (vs: Python 1991, Java 1995),
tại Google.</p>
<p>Go được thiết kế ra với mục tiêu thay thế cho C++, nhưng khi tung ra cộng
đồng, nó lại trở thành ngôn ngữ hấp dẫn đối với các lập trình viên dùng
ngôn ngữ bậc cao hơn như <a href="http://www.catb.org/~esr/reposurgeon/GoNotes.html">Python</a>,
<a href="https://blog.iron.io/how-we-went-from-30-servers-to-2-go/">Ruby</a>, PHP, NodeJS... nhờ khả năng chạy code
nhanh, dùng ít tài nguyên hơn, deploy dễ hơn so với các ngôn ngữ này.</p>
<p>Go trở thành một trào lưu (trend) công nghệ trên internet, với các bài viết
"Write X in Go" luôn trở thành bài hot (và giờ thì tới Rust).
Go được sử dụng như một ngôn ngữ "backend", rất thịnh hành tại các startup
công nghệ để viết "service" trong các hệ thống "microservice", web API.
<a href="https://blog.golang.org/survey2019-results">Go còn được dùng phổ biến để viết các câu lệnh command line</a>.</p>
<p>Go đã ở giai đoạn "production ready", đủ ổn định và đã được chạy trên các <a href="https://github.com/golang/go/wiki/GoUsers">hệ thống
lớn trên toàn cầu</a>.
Các sản phẩm opensource viết bằng Go được dùng rộng rãi như:
Kubernetes, Docker, Terraform, InfluxDB, Prometheus, Grafana, ...
Go trở thành ngôn ngữ được ưa chuộng và phổ biến trên các dịch vụ cloud,
devops.</p>
<p>Những lĩnh vực khác cũng đã có mặt Go nhưng chưa thực sự thành công: mobile,
frontend (JavaScript), làm website (như Django/RubyOnRails), Machine Learning.</p>
<h2>Những ưu điểm nổi bật của Go</h2>
<ul>
<li>Ngôn ngữ đơn giản: Go có ít khái niệm hơn các ngôn ngữ lập trình khác
C++/Java/Python/Ruby... Go tại năm 2020 có <a href="https://golang.org/ref/spec#Keywords">25
keywords</a>, <a href="https://docs.python.org/3.8/reference/lexical_analysis.html#keywords">Python 3.8 có 35
keywords</a>.
Hầu hết các công ty tuyển lập trình viên Go đều không yêu cầu kinh nghiệm code
Go,
chỉ cần tuyển 1 lập trình viên đã dùng ngôn ngữ khác, qua training 1-2 tuần
là đã có thể viết code production, trông không khác gì lập trình viên lâu năm.</li>
<li>Code Go viết bằng chính Go, ai cũng có thể đọc <a href="https://github.com/golang/go/blob/master/src/runtime/map.go">kiểu dữ liệu map được viết thế nào</a> - so với Python (CPython) sẽ phải đọc code C.</li>
<li>Hệ thống thư viện có sẵn (stardard library) đa dạng, đầy đủ - ngang ngửa Python, thậm chí còn hơn: gửi HTTP request chỉ cần dùng <code>net/http</code>, không phải <a href="http://pp.pymi.vn/article/requests/">cài requests như Python (mặc dù Python có thể dùng urllib nhưng không mấy ai dùng)</a>, đầy đủ <code>json</code>, <code>regexp</code>, cho tới HTTP server sẵn sàng chạy production.</li>
<li>Compile nhanh: so với các ngôn ngữ C/C++/Java/C#... thì Go compile nhanh gấp
nhiều lần</li>
<li>Sản phẩm compile tạo ra là 1 file binary. Sau đó
chỉ cần mang file này đi chạy là xong - điểm này khác biệt lớn so với Python,
NodeJS, Ruby - phải cài các "dependency" (pip/npm/gem) rồi mới chạy được code (Go cũng cần
tải các dependency, nhưng chỉ cần thực hiện trước khi compile ra file binary).</li>
<li>Code chạy nhanh: Go tuy không nhanh bằng C/C++/C#/Java trong hầu hết các
trường hợp, chậm hơn cỡ 2-5 lần, nhưng nhanh hơn Python/Ruby cỡ <a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/which-programs-are-fastest.html">20-100 lần</a>,</li>
<li>Tốn ít memory - bộ nhớ: ít hơn <a href="https://blog.iron.io/how-we-went-from-30-servers-to-2-go/">Ruby cỡ 15 lần</a></li>
<li>Quản lý bộ nhớ tự động (tương tự Python/Ruby/Java...)</li>
<li>Dễ viết code concurrency, dùng multi-core (thay vì dùng thread/async).</li>
</ul>
<h2>Những khác biệt chủ yếu với Python</h2>
<h3>Cách viết và chạy code</h3>
<p>Go là compiled language, trước khi chạy được code phải qua 1 bước compile để
tạo ra file binary, rồi sau đó mới chạy được file này.
<a href="http://pp.pymi.vn/article/repl/">Go không có sẵn REPL, không bật lên gõ code trực tiếp như Python được</a>.
Mỗi lần sửa gì, thử gì, phải compile lại rồi mới chạy được.</p>
<div class="highlight"><pre><span></span><code>go build
./filename
</code></pre></div>
<p>Vậy nên cách nhanh nhất để thử 1 đoạn code trong Go có lẽ là viết unittest.
<a href="https://golang.org/doc/code.html#Testing">test rất phổ biến với một Go project</a>,
là một phần có sẵn trong bộ công cụ dev.</p>
<p>Với 1 file code đơn giản, có thể dùng lệnh <code>go run main.go</code> để làm gộp 2 bước,
compile rồi chạy file kết quả luôn.</p>
<h3>Auto format - go fmt</h3>
<p>Go là ngôn ngữ lập trình đầu tiên đưa 1 công cụ format code tự động vào tiêu chuẩn,
chấm dứt mọi tranh cãi về code-style/format code. Ý tưởng tuyệt vời này sau được
copy sang nhiều ngôn ngữ khác (như <a href="http://pp.pymi.vn/article/black/">Python black</a>)</p>
<div class="highlight"><pre><span></span><code>go fmt
</code></pre></div>
<h4>Chú ý</h4>
<p>Không xuống dòng tùy tiện trong <code>()</code> như Python (Vd: khi gọi function). Ví dụ
sau sẽ <a href="https://play.golang.org/p/dluh98DPVIR">không compile</a>:</p>
<div class="highlight"><pre><span></span><code><span class="nx">fmt</span><span class="p">.</span><span class="nx">Print</span><span class="p">(</span>
<span class="s">"Hello, world!"</span>
<span class="p">)</span>
</code></pre></div>
<p>Go <a href="https://golang.org/ref/spec#Semicolons">tự động thêm <code>;</code> vào các dòng
khi nó xử lý file code</a>. Tốt nhất là
viết hết trên 1 dòng rồi để gofmt lo chuyện format.</p>
<h3>Static typing</h3>
<p>Python là ngôn ngữ dynamic typing, Go là static typing.
Nếu chưa từng code ngôn ngữ static typing hay chưa dùng type annotation của Python,
khác biệt này sẽ gây chút khó khăn lúc bắt đầu code Go.</p>
<p>Theo <a href="https://wiki.python.org/moin/Why%20is%20Python%20a%20dynamic%20language%20and%20also%20a%20strongly%20typed%20language">python wiki</a></p>
<blockquote>
<p>In a statically typed language, the type of variables must be known (and
usually declared) at the point at which it is used. Attempting to use
it will be an error. In a dynamically typed language, objects still have a
type, but it is determined at runtime. You are free to bind names (variables)
to different objects with a different type. So long as you only perform
operations valid for the type the interpreter doesn't care what type they
actually are.</p>
</blockquote>
<p>Static/dynamic typing nói tới kiểu (type) của 1 biến (variable/name).
Trong dynamic typing, kiểu của 1 biến có thể thay đổi:</p>
<div class="highlight"><pre><span></span><code><span class="n">x</span> <span class="o">=</span> <span class="mi">10</span>
<span class="n">x</span> <span class="o">=</span> <span class="s2">"PyMi"</span>
</code></pre></div>
<p>Trong static typing, kiểu của 1 biến là cố định, và được khai báo ngay từ
trước khi dùng, code sau sẽ gặp lỗi khi compile, và không tạo ra binary nào để chạy.</p>
<div class="highlight"><pre><span></span><code><span class="kd">var</span> <span class="nx">x</span> <span class="kt">int</span> <span class="p">=</span> <span class="mi">10</span>
<span class="nx">x</span> <span class="p">=</span> <span class="s">"PyMi"</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code>cannot use <span class="s2">"PyMi"</span> <span class="o">(</span><span class="nb">type</span> untyped string<span class="o">)</span> as <span class="nb">type</span> int <span class="k">in</span> assignment
</code></pre></div>
<p>Trong Python3.6+, <a href="http://pp.pymi.vn/article/mypy/">sử dụng type annotation, kết hợp với mypy</a>
để check cũng sẽ
cho lỗi tương tự (nhưng vẫn chạy được).</p>
<div class="highlight"><pre><span></span><code><span class="n">x</span><span class="p">:</span> <span class="nb">int</span> <span class="o">=</span> <span class="mi">10</span>
<span class="n">x</span> <span class="o">=</span> <span class="s2">"PyMi"</span>
<span class="nb">print</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code>$ mypy typetest.py
typetest.py:2: error: Incompatible types <span class="k">in</span> assignment <span class="o">(</span>expression has <span class="nb">type</span> <span class="s2">"str"</span>, variable has <span class="nb">type</span> <span class="s2">"int"</span><span class="o">)</span>
Found <span class="m">1</span> error <span class="k">in</span> <span class="m">1</span> file <span class="o">(</span>checked <span class="m">1</span> <span class="nb">source</span> file<span class="o">)</span>
</code></pre></div>
<p>Kiểu của các biến trong Go nói chung phải khai báo (declare), Go có thể tự suy
luận (type inference) được trong một vài trường hợp đơn giản.</p>
<div class="highlight"><pre><span></span><code><span class="nx">x</span> <span class="o">:=</span> <span class="mi">10</span>
<span class="nx">s</span> <span class="o">:=</span> <span class="s">"PyMi"</span>
</code></pre></div>
<h3>Low level</h3>
<p>Được thiết kế nhằm thay C/C++ pha lẫn sự đơn giản dễ đọc của Python,
code Go thường đơn giản hơn code C/C++ nhưng khá "thủ công"/"low level"
so với code Python. Lập trình viên Python code Go nên quen với việc bỏ bớt
đi nhiều tính năng tiện lợi, phải viết nhiều code hơn.</p>
<ul>
<li>Go không có list comprehension</li>
<li>Go không có map/filter</li>
<li>Go không có kiểu set</li>
<li>Go không có <code>4 in [2,3,4]</code> để <a href="https://stackoverflow.com/questions/38654383/how-to-search-for-an-element-in-a-golang-slice">kiểm tra phần tử có trong list không</a></li>
<li>Sort trong Go chắc chắn dài hơn <code>L.sort(reverse=True)</code></li>
<li>Reverse string sẽ không phải là<code>s[::-1]</code> mà <a href="https://stackoverflow.com/questions/1752414/how-to-reverse-a-string-in-go">dài cả mét</a></li>
<li>Không đơn giản chỉ <code>json.loads</code> để biến <code>str</code> thành <code>dict</code> mà phải định
nghĩa struct theo cấu trúc của JSON.</li>
</ul>
<p>Code Go nhìn chung sẽ dài hơn code Python, vậy nên lập trình viên nên sắm cho
mình một IDE xịn (như GoLand/IntelliJ IDEA , VSCode hay <code>vim</code> + <code>vim-go</code>), với khả năng <a href="https://code.visualstudio.com/docs/editor/userdefinedsnippets">dùng snippet để
sinh code</a>.</p>
<p>Sự bất tiện này sẽ đỡ khó chịu đi nhiều khi đã quen dùng snippet.</p>
<h2>Học lập trình Go (khi đã biết Python)</h2>
<h3>Bao nhiêu là đủ? Em vui là được có phải không!</h3>
<p>Không ai đọc hết quyển từ điển tiếng Việt rồi mới ra nói câu đầu tiên,
không ai học lên tiến sỹ âm nhạc rồi mới chạm tay vào đánh đàn. Khi <a href="https://pymi.vn">học Python
tại PyMi.vn</a>, ta học từng chút, dùng từng chút, chứ không học hết lý thuyết
cả Python rồi mới thực hành. Cũng không phải học <code>async</code>, <code>threading</code>, <a href="https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python">metaclass</a>
rồi mới chịu đi làm.</p>
<p>Học Go cũng vậy, dùng gì học đấy, cần gì học đấy.
Cần gì thì phụ thuộc vào bạn định làm gì, một Python web developer sẽ học
Django, ORM, làm việc với database trong khi một SysAdmin/DevOps engineer lại
làm việc với file, process...</p>
<h3>Tài liệu</h3>
<ul>
<li>Tài liệu chính thống, đầy đủ và hiệu quả để học go là Go Tour: <a href="https://tour.golang.org/">https://tour.golang.org/</a></li>
<li>Bài tập có giải trên <a href="https://exercism.io/tracks/go">exercism.io</a> - chọn practice mode.</li>
<li>FAQ giải đáp các thắc mắc thường gặp: <a href="https://golang.org/doc/faq">https://golang.org/doc/faq</a></li>
<li>Effective Go hướng dẫn viết Go theo cách của Gophers: <a href="https://golang.org/doc/effective_go.html">https://golang.org/doc/effective_go.html</a></li>
<li>Các tài liệu khác: <a href="https://github.com/golang/go/wiki/Learn">learn</a></li>
</ul>
<h3>Viết code Go</h3>
<p><a href="https://golang.org/doc/tutorial/getting-started">Cài Go lên máy rồi viết code ra file</a>, hoặc code online trên trang Play <a href="https://play.golang.org/">https://play.golang.org/</a></p>
<p>Go chạy code từ function <code>main</code> thuộc <code>package main</code>. code "Hello world!" như sau:</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="s">"fmt"</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">"Hello, world."</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<h3>Naming</h3>
<p>Go dùng <code>camelCase</code>, với ý nghĩa đặc biệt khi chữ cái đầu viết hoa hay viết thường.
Nếu viết hoa, var/function/type đó sẽ trở thành "public", code bên ngoài package hiện tại truy cập được, còn không viết hoa sẽ là "private".</p>
<h3>Data types</h3>
<h4>Built-in</h4>
<ul>
<li>nil: <code>nil</code> là một giá trị, không có kiểu, đại diện cho sự "không tồn tại".</li>
<li>bool: kiểu boolean gồm 2 giá trị <code>true</code> <code>false</code>, các boolean operator <code>&&</code> (and), <code>||</code> (or) tuân theo <a href="https://pymi.vn/tutorial/boolean/"><code>short-circuit</code></a>.</li>
<li>int: các kiểu số trong Go đều có kích thước, int có kích thước 32 hoặc 64 bits <a href="https://golang.org/doc/faq#q_int_sizes">tùy theo bản (chủ yếu 64 bits)</a>.
Các kiểu cụ thể <code>int8 int16 int32 int64</code>, có kích thước là 2 mũ n, int8 int16 thường chỉ dùng khi tối ưu về memory. int64 biểu diễn được giá trị trong khoảng (<code>-2**64/2, 2**64/2</code>), muốn tính giá trị lớn có thể <a href="https://golang.org/pkg/math/big/#Int.String">sử dụng package có sẵn <code>math/big</code></a>.</li>
<li><code>float32</code> hoặc <code>float64</code>: chú ý không có kiểu <code>float</code>. <a href="https://pymi.vn/blog/why-not-float/">Tuân theo chuẩn IEE754</a> nên <code>x, y, z := 0.1, 0.1, 0.1</code> thì <code>x + y + z == 0.3</code> sẽ trả về false. Chú ý ở trên tạo các biến để các giá trị 0.1 có kiểu float64, nếu viết trực tiếp <code>0.1 + 0.1 + 0.1 == 0.3</code> sẽ là so sánh constants và trả về true <a href="https://play.golang.org/p/TWRRr_lM7jk">https://play.golang.org/p/TWRRr_lM7jk</a>. Đọc thêm về constants tại <a href="https://blog.golang.org/constants">blog Go</a>.</li>
<li>string: giống như string của Python, immutable, có thể truy cập các chữ cái <a href="https://blog.golang.org/strings">bằng cách chuyển thành runes</a> (<code>s := []rune(sting)</code>) rồi dùng index: <code>s[3]</code>. String bên dưới là 1 chuỗi các byte, hay một byte array/byte slice <code>[]byte{'h', 'e', 'l', 'l', 'o'}</code>, có thể convert thành string: <code>string(bytes)</code>. Python cũng có kiểu <code>bytes</code>, cũng chuyển thành <code>str</code> bằng cách decode <code>b'abc'.decode('utf-8')</code>.</li>
<li>array/slice: Go array giống như C, các phần tử phải cùng kiểu, và kích thước cố định không đổi. <a href="https://www.youtube.com/watch?v=5DVV36uqQ4E">Array ít được dùng trực tiếp</a>
nó được dùng bên dưới slice và slice linh hoạt như list trong Python. Ví dụ về slice</li>
</ul>
<div class="highlight"><pre><span></span><code> <span class="nx">ns</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">}</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%v\n"</span><span class="p">,</span> <span class="nx">ns</span><span class="p">)</span>
<span class="nx">ns</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">ns</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%v\n"</span><span class="p">,</span> <span class="nx">ns</span><span class="p">)</span>
<span class="nx">ns</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">ns</span><span class="p">,</span> <span class="nx">ns</span><span class="o">...</span><span class="p">)</span> <span class="c1">// như extend trong python list</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%v\n"</span><span class="p">,</span> <span class="nx">ns</span><span class="p">)</span>
</code></pre></div>
<p><a href="https://play.golang.org/p/SCW8F0EqCmW">https://play.golang.org/p/SCW8F0EqCmW</a></p>
<ul>
<li>map: giống dict của Python, key phải so sánh <code>==</code> được, map và slice không
làm key được, thứ tự key là ngẫu nhiên. Map trong Go dùng khi cần nối
key-value, tìm kiếm nhanh, nhưng không dùng như 1 object như dict Python
<code>{"name": "Pymier", "age": 20}</code>,
do map phải có kiểu cố định cho key, value. <a href="https://play.golang.org/p/JRu3lp_mEGj">Xem code</a></li>
</ul>
<div class="highlight"><pre><span></span><code> <span class="nx">users</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">(</span><span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="kt">string</span><span class="p">)</span>
<span class="nx">users</span><span class="p">[</span><span class="mi">20081269</span><span class="p">]</span> <span class="p">=</span> <span class="s">"Pymier69"</span>
<span class="nx">users</span><span class="p">[</span><span class="mi">20081234</span><span class="p">]</span> <span class="p">=</span> <span class="s">"Pymier1"</span>
<span class="nx">users</span><span class="p">[</span><span class="mi">20081239</span><span class="p">]</span> <span class="p">=</span> <span class="s">"Pymier2"</span>
<span class="k">for</span> <span class="nx">k</span><span class="p">,</span> <span class="nx">v</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">users</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%d: %s\n"</span><span class="p">,</span> <span class="nx">k</span><span class="p">,</span> <span class="nx">v</span><span class="p">)</span>
<span class="p">}</span>
<span class="nb">println</span><span class="p">(</span><span class="nx">users</span><span class="p">[</span><span class="mi">20081239</span><span class="p">])</span>
</code></pre></div>
<p>Cú pháp <a href="https://golang.org/ref/spec#Composite_literals"><code>composite literal</code></a> giúp tạo giá trị cho struct/slice/array/map.</p>
<div class="highlight"><pre><span></span><code> <span class="nx">xs</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{</span><span class="mi">3</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">1</span><span class="p">}</span>
<span class="nx">m</span> <span class="o">:=</span> <span class="kd">map</span><span class="p">[</span><span class="kt">int</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span>
<span class="mi">123</span><span class="p">:</span> <span class="s">"foo"</span><span class="p">,</span>
<span class="mi">345</span><span class="p">:</span> <span class="s">"bar"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div>
<p>Các kiểu khác <code>struct</code>, <code>pointer</code>, <code>function</code>, <code>interface</code>, <code>channel</code>
sẽ được nhắc tới sau, xem đầy đủ tại <a href="https://golang.org/ref/spec#Types">spec</a>.</p>
<h4>Struct & method</h4>
<p>Python có class, cung cấp đủ các tính năng tiêu chuẩn của OOP (object-oriented
programming - lập trình hướng đối tượng). Định nghĩa 1 class tạo ra 1
kiểu dữ liệu mới và cho phép đóng gói dữ liệu (data) với tính năng (method) lại
với nhau.</p>
<p>Go <code>struct</code>: A struct is a collection of fields.</p>
<p>struct không cung cấp các tính năng của OOP (như inheritance), nhưng cũng
tạo ra 1 kiểu dữ liệu mới và cho phép đóng gói dữ liệu (data) với tính năng
(method) lại với nhau. <a href="https://play.golang.org/p/tQMPLn9OXcj">https://play.golang.org/p/tQMPLn9OXcj</a></p>
<div class="highlight"><pre><span></span><code><span class="kd">type</span> <span class="nx">Rectangle</span> <span class="kd">struct</span> <span class="p">{</span>
<span class="nx">Width</span> <span class="kt">float64</span>
<span class="nx">Height</span> <span class="kt">float64</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="p">(</span><span class="nx">r</span> <span class="nx">Rectangle</span><span class="p">)</span> <span class="nx">Area</span><span class="p">()</span> <span class="kt">float64</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">r</span><span class="p">.</span><span class="nx">Width</span> <span class="o">*</span> <span class="nx">r</span><span class="p">.</span><span class="nx">Height</span>
<span class="p">}</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">r</span> <span class="o">:=</span> <span class="nx">Rectangle</span><span class="p">{</span><span class="nx">Width</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">Height</span><span class="p">:</span> <span class="mi">3</span><span class="p">}</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%f %T"</span><span class="p">,</span> <span class="nx">r</span><span class="p">,</span> <span class="nx">r</span><span class="p">.</span><span class="nx">Area</span><span class="p">())</span>
<span class="p">}</span>
</code></pre></div>
<p>Code tương tự trong Python3, với type annotation</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Rectangle</span><span class="p">():</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">width</span><span class="p">:</span> <span class="nb">float</span><span class="p">,</span> <span class="n">height</span><span class="p">:</span> <span class="nb">float</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">width</span> <span class="o">=</span> <span class="n">width</span>
<span class="bp">self</span><span class="o">.</span><span class="n">height</span> <span class="o">=</span> <span class="n">height</span>
<span class="k">def</span> <span class="nf">area</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span> <span class="o">-></span> <span class="nb">float</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">width</span> <span class="o">*</span> <span class="bp">self</span><span class="o">.</span><span class="n">height</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="nb">print</span><span class="p">(</span><span class="n">Rectangle</span><span class="p">(</span><span class="n">width</span><span class="o">=</span><span class="mi">2</span><span class="p">,</span> <span class="n">height</span><span class="o">=</span><span class="mi">3</span><span class="p">)</span><span class="o">.</span><span class="n">area</span><span class="p">())</span>
</code></pre></div>
<p>Trong Go, function gắn với các struct gọi là method, <code>(r Rectangle)</code> gọi là receiver, đứng trước tên <code>Area()</code> trông lạ so với các ngôn ngữ khác, nhưng nó đóng vai trò như <code>self</code> trong Python method.</p>
<blockquote>
<p>Remember: a method is just a function with a receiver argument.</p>
</blockquote>
<h4>interface, type assertion, type switch</h4>
<p>Một interface type định nghĩa 1 tập hợp các method.</p>
<blockquote>
<p>An interface type is defined as a set of method signatures.</p>
</blockquote>
<p>Một value của type interface có thể chứa bất kỳ giá trị nào implement các method qui định trong interface đó.</p>
<p>Empty interface <code>interface{}</code> là trường hợp đặc biệt, nó có thể chứa mọi giá
trị do không cần có method nào.</p>
<p>Một value kiểu <code>interface{}</code> đánh sập mọi đảm bảo về type trong Golang, biến
Go thành dynamic typing như Python. Nó như lối thoát linh hoạt giữa mọi
sự cứng nhắc/static.</p>
<p>Sử dụng type assertion để truy cập giá trị ẩn dưới <code>interface{}</code>:</p>
<div class="highlight"><pre><span></span><code> <span class="kd">var</span> <span class="nx">anything</span> <span class="kd">interface</span><span class="p">{}</span>
<span class="nx">anything</span> <span class="p">=</span> <span class="s">"PyMi.vn"</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%v %T\n"</span><span class="p">,</span> <span class="nx">anything</span><span class="p">,</span> <span class="nx">anything</span><span class="p">)</span>
<span class="nx">anything</span> <span class="p">=</span> <span class="mi">42</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%v %T\n"</span><span class="p">,</span> <span class="nx">anything</span><span class="p">,</span> <span class="nx">anything</span><span class="p">)</span>
<span class="nx">x</span> <span class="o">:=</span> <span class="mi">10</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%v %T\n"</span><span class="p">,</span> <span class="nx">x</span><span class="o">+</span><span class="nx">anything</span><span class="p">.(</span><span class="kt">int</span><span class="p">),</span> <span class="nx">anything</span><span class="p">.(</span><span class="kt">int</span><span class="p">))</span>
</code></pre></div>
<p><a href="https://play.golang.org/p/mNXTI-4C3pU">https://play.golang.org/p/mNXTI-4C3pU</a></p>
<p>hoặc dùng <code>switch</code> để xử lý các kiểu khác nhau:</p>
<div class="highlight"><pre><span></span><code> <span class="k">switch</span> <span class="nx">v</span> <span class="o">:=</span> <span class="nx">i</span><span class="p">.(</span><span class="kd">type</span><span class="p">)</span> <span class="p">{</span>
<span class="k">case</span> <span class="kt">int</span><span class="p">:</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"Twice %v is %v\n"</span><span class="p">,</span> <span class="nx">v</span><span class="p">,</span> <span class="nx">v</span><span class="o">*</span><span class="mi">2</span><span class="p">)</span>
<span class="k">case</span> <span class="kt">string</span><span class="p">:</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%q is %v bytes long\n"</span><span class="p">,</span> <span class="nx">v</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="nx">v</span><span class="p">))</span>
</code></pre></div>
<h3>Control flow (if/else/for)</h3>
<p>Go không có while, dùng for để loop đủ kiểu. Để lặp vô hạn như while, dùng <code>for { }</code>.</p>
<p>if cho phép định nghĩa một biến chỉ dùng trong if, khá giống với warus operator của Python 3.8.</p>
<div class="highlight"><pre><span></span><code> <span class="nx">y</span> <span class="o">:=</span> <span class="mi">3</span>
<span class="k">if</span> <span class="nx">x</span> <span class="o">:=</span> <span class="nx">y</span> <span class="o">+</span> <span class="mi">2</span><span class="p">;</span> <span class="nx">x</span> <span class="p">></span> <span class="mi">2</span> <span class="p">{</span>
<span class="nb">println</span><span class="p">(</span><span class="s">"big number"</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="nb">println</span><span class="p">(</span><span class="s">"We don't talk any more"</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">ns</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span><span class="s">"meo"</span><span class="p">,</span> <span class="s">"bo"</span><span class="p">,</span> <span class="s">"ga"</span><span class="p">}</span>
<span class="k">for</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">value</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">ns</span> <span class="p">{</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="nx">value</span><span class="p">)</span> <span class="p"><</span> <span class="mi">3</span> <span class="p">{</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%d %s\n"</span><span class="p">,</span> <span class="nx">key</span><span class="p">,</span> <span class="nx">value</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div>
<p><a href="https://play.golang.org/p/rHJP4ghomzo">https://play.golang.org/p/rHJP4ghomzo</a></p>
<h3>Function</h3>
<p>Định nghĩa function không khác Python đáng kể, ngoại trừ việc thay vì return tuple, Go có thể return nhiều giá trị (và không có kiểu tuple).</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span> <span class="nx">TwiceAndThrice</span><span class="p">(</span><span class="nx">x</span> <span class="kt">int</span><span class="p">)</span> <span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">int</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">2</span><span class="p">,</span> <span class="nx">x</span> <span class="o">*</span> <span class="mi">3</span>
<span class="p">}</span>
</code></pre></div>
<p><a href="https://golang.org/doc/faq#overloading">Go không hỗ trợ overloading - một function dùng với số lượng argument khác nhau</a>
, nên cũng không hỗ trợ default argument <code>pyfunc(x, y=10)</code>.</p>
<p>Cách duy nhất để gọi function là đưa vào các argument theo thứ tự, không
có <a href="https://docs.python.org/3/tutorial/controlflow.html#keyword-arguments">keyword argument như Python</a> (<code>python_function(x=5, y=7)</code>).</p>
<p>Có thể dùng function làm argument của function khác, việc này rất phổ biến trong Go, khái niệm này có tên <code>first class function</code>.
Function nhận function khác làm argument được gọi là <code>higher order function</code>.</p>
<h3>Error handling</h3>
<p>Go không có exception, lỗi không được xử lý khiến chương trình kết thúc qua việc gọi function "panic" (runtime error).
Ví dụ khi dùng type assertion để truy cập kiểu string dưới 1 empty interface chứa giá trị int.</p>
<div class="highlight"><pre><span></span><code><span class="nx">panic</span><span class="p">:</span> <span class="kd">interface</span> <span class="nx">conversion</span><span class="p">:</span> <span class="kd">interface</span> <span class="p">{}</span> <span class="nx">is</span> <span class="kt">int</span><span class="p">,</span> <span class="nx">not</span> <span class="kt">string</span>
</code></pre></div>
<p>Các function thay vì tạo ra exception, thường trả về giá trị kiểu <code>error</code> kèm kết quả.
Ví dụ function trong package <code>strconv</code> dùng để convert string thành int:</p>
<div class="highlight"><pre><span></span><code><span class="kd">func</span> <span class="nx">Atoi</span><span class="p">(</span><span class="nx">s</span> <span class="kt">string</span><span class="p">)</span> <span class="p">(</span><span class="kt">int</span><span class="p">,</span> <span class="kt">error</span><span class="p">)</span>
</code></pre></div>
<p>Nếu thành công, error sẽ là <code>nil</code> còn biến kiểu int chứa giá trị, nếu s không phải dạng string của 1 số int, error sẽ có giá trị khác <code>nil</code> và thường chứa chi tiết về lỗi xảy ra. <code>error</code> là kiểu dữ liệu interface.</p>
<div class="highlight"><pre><span></span><code><span class="kd">type</span> <span class="kt">error</span> <span class="kd">interface</span> <span class="p">{</span>
<span class="nx">Error</span><span class="p">()</span> <span class="kt">string</span>
<span class="p">}</span>
</code></pre></div>
<p>mọi kiểu dữ liệu có method <code>Error()</code> return <code>string</code> đều có thể là 1 <code>error</code>.</p>
<p>Do các function đều viết theo cách này, nên code gọi 1 function trong Go thường đi kèm với 1 đoạn kiểm tra <code>error</code> ngay sau đó rồi mới xử lý giá trị nhận được:</p>
<div class="highlight"><pre><span></span><code> <span class="nx">s</span> <span class="o">:=</span> <span class="s">"42"</span>
<span class="nx">value</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">strconv</span><span class="p">.</span><span class="nx">Atoi</span><span class="p">(</span><span class="nx">s</span><span class="p">)</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="nb">println</span><span class="p">(</span><span class="nx">value</span> <span class="o">/</span> <span class="mi">2</span><span class="p">)</span>
</code></pre></div>
<p>Cách làm này gây nhiều tranh cãi, nhưng vẫn là cách làm chính thống của Go.
Go dễ dãi hơn so với Java (khi 1 function xảy ra exception gì thì phải khai báo có thể xảy ra exception, và code gọi bắt buộc phải xử lý).
Bỏ qua giá trị của <code>err</code> là chuyện hoàn toàn làm được, như viết code Python không xử lý exception, khi có error xảy ra, chương trình thường sẽ... chết.</p>
<p>Cách return error này không ÉP được lập trình viên phải xử lý mọi lỗi, nhưng tạo ra 1 nền văn hóa trong cộng đồng code Golang: luôn xử lý (hay ít nhất là nghĩ tới) error mọi lúc, mọi nơi.</p>
<h3>Import</h3>
<p>Cú pháp tương tự Python</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="s2">"packagename"</span>
<span class="kn">import</span> <span class="s2">"fmt"</span>
</code></pre></div>
<p>rồi gọi function qua <code>package.Function</code>, vd: <code>fmt.Println</code></p>
<h3>Package & Install 3rd packages</h3>
<p>Đầu mỗi file code Go phải bắt đầu bằng</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span> <span class="nx">packagename</span>
</code></pre></div>
<p>Tất cả các file go trong cùng 1 thư mục (không tính thư mục con) phải khai
báo cùng package, chúng sẽ được gộp lại làm một (trừ phần import, mỗi file
phải tự import thư viện mình dùng). Có thể coi việc các file khác nhau chỉ để
thuận tiện tổ chức code, chứ vẫn là trong 1 file. Các function trong cùng 1
package (khác file) có thể gọi nhau thoải mái, không cần import lẫn nhau.</p>
<p>Việc này khác với Python, mỗi file.py tự động là 1 module riêng biệt.</p>
<p>Go cài package bằng lệnh <code>go get</code>, sau đó import tên package - là một đường dẫn theo cấu trúc URL online.</p>
<div class="highlight"><pre><span></span><code>$ go get github.com/stretchr/testify/assert
</code></pre></div>
<p>Package "testing" của Go không có "assert", cài package từ github rồi dùng
assert.Equal để kiểm tra 2 giá trị có bằng nhau không, và message hiển thị khi chúng không bằng nhau.</p>
<div class="highlight"><pre><span></span><code><span class="c1">// main_test.go</span>
<span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="p">(</span>
<span class="s">"github.com/stretchr/testify/assert"</span>
<span class="s">"testing"</span>
<span class="p">)</span>
<span class="kd">func</span> <span class="nx">TestSomething</span><span class="p">(</span><span class="nx">t</span> <span class="o">*</span><span class="nx">testing</span><span class="p">.</span><span class="nx">T</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// assert equality</span>
<span class="nx">assert</span><span class="p">.</span><span class="nx">Equal</span><span class="p">(</span><span class="nx">t</span><span class="p">,</span> <span class="mi">123</span><span class="p">,</span> <span class="mi">123</span><span class="p">,</span> <span class="s">"they should be equal"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code> $ go <span class="nb">test</span> -v
<span class="o">===</span> RUN TestSomething
--- PASS: TestSomething <span class="o">(</span><span class="m">0</span>.00s<span class="o">)</span>
PASS
ok _/home/hvn/MyData <span class="m">0</span>.002s
</code></pre></div>
<p>Danh sách các package xem tại <a href="https://github.com/avelino/awesome-go">awesome-go</a> và <a href="https://godoc.org/">godoc</a></p>
<h3>IO: read/write file</h3>
<p>Đọc file từng dòng và ghi file:</p>
<p>Ghi file:</p>
<ul>
<li>Tạo file với os.Create, thu được 1 File struct</li>
<li>Gọi method File.WriteString để ghi string</li>
</ul>
<div class="highlight"><pre><span></span><code> <span class="nx">file</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Create</span><span class="p">(</span><span class="s">"bundau.mamtom"</span><span class="p">)</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="nx">file</span><span class="p">.</span><span class="nx">Close</span><span class="p">()</span>
<span class="nx">_</span><span class="p">,</span> <span class="nx">err</span> <span class="p">=</span> <span class="nx">file</span><span class="p">.</span><span class="nx">WriteString</span><span class="p">(</span><span class="s">"bundau\n"</span><span class="p">)</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>Nếu bỏ qua phần xử lý error thì cũng không khác Python là mấy.</p>
<p>Đọc file:</p>
<ul>
<li>Mở file bằng os.Open, thu được 1 File struct</li>
<li>Tạo 1 "Scanner" để giúp xử lý logic đọc file theo từng dòng</li>
<li>Lặp qua scanner.Scan() để lấy từng dòng qua scanner.Text()</li>
</ul>
<div class="highlight"><pre><span></span><code> <span class="nx">file</span><span class="p">,</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">os</span><span class="p">.</span><span class="nx">Open</span><span class="p">(</span><span class="s">"bundau.mamtom"</span><span class="p">)</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="nx">file</span><span class="p">.</span><span class="nx">Close</span><span class="p">()</span>
<span class="nx">scanner</span> <span class="o">:=</span> <span class="nx">bufio</span><span class="p">.</span><span class="nx">NewScanner</span><span class="p">(</span><span class="nx">file</span><span class="p">)</span>
<span class="kd">var</span> <span class="nx">lines</span> <span class="p">[]</span><span class="kt">string</span>
<span class="k">for</span> <span class="nx">scanner</span><span class="p">.</span><span class="nx">Scan</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">lines</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">lines</span><span class="p">,</span> <span class="nx">scanner</span><span class="p">.</span><span class="nx">Text</span><span class="p">())</span>
<span class="p">}</span>
<span class="k">if</span> <span class="nx">err</span> <span class="o">:=</span> <span class="nx">scanner</span><span class="p">.</span><span class="nx">Err</span><span class="p">();</span> <span class="nx">err</span> <span class="o">!=</span> <span class="kc">nil</span> <span class="p">{</span>
<span class="nx">log</span><span class="p">.</span><span class="nx">Fatal</span><span class="p">(</span><span class="nx">err</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p><a href="https://play.golang.org/p/Jv80_bRJc2H">https://play.golang.org/p/Jv80_bRJc2H</a></p>
<h2>Hành động của chúng ta</h2>
<p>Giải bài <a href="https://projecteuler.net/problem=1">Project Euler 1</a> bằng Go:</p>
<blockquote>
<p>Find the sum of all the multiples of 3 or 5 below 1000.</p>
</blockquote>
<div class="highlight"><pre><span></span><code><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="s">"fmt"</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">sum</span> <span class="o">:=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="nx">i</span> <span class="o">:=</span> <span class="mi">0</span><span class="p">;</span> <span class="nx">i</span> <span class="p"><</span> <span class="mi">1000</span><span class="p">;</span> <span class="nx">i</span><span class="o">++</span> <span class="p">{</span>
<span class="k">if</span> <span class="nx">i</span><span class="o">%</span><span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="nx">i</span><span class="o">%</span><span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span> <span class="p">{</span>
<span class="nx">sum</span> <span class="p">=</span> <span class="nx">sum</span> <span class="o">+</span> <span class="nx">i</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%d\n"</span><span class="p">,</span> <span class="nx">sum</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<p>Giải bài <a href="https://projecteuler.net/problem=16">Project Euler 16</a></p>
<blockquote>
<p>What is the sum of the digits of the number 2 to the power of 1000?</p>
</blockquote>
<div class="highlight"><pre><span></span><code><span class="kn">package</span> <span class="nx">main</span>
<span class="kn">import</span> <span class="p">(</span>
<span class="s">"fmt"</span>
<span class="s">"math/big"</span>
<span class="s">"strconv"</span>
<span class="p">)</span>
<span class="kd">func</span> <span class="nx">main</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">twoToThePowerOf1000</span> <span class="nx">big</span><span class="p">.</span><span class="nx">Int</span>
<span class="nx">twoToThePowerOf1000</span><span class="p">.</span><span class="nx">Exp</span><span class="p">(</span><span class="nx">big</span><span class="p">.</span><span class="nx">NewInt</span><span class="p">(</span><span class="mi">2</span><span class="p">),</span> <span class="nx">big</span><span class="p">.</span><span class="nx">NewInt</span><span class="p">(</span><span class="mi">1000</span><span class="p">),</span> <span class="kc">nil</span><span class="p">)</span>
<span class="c1">// Có thể tính sum luôn nhưng bài này minh họa slice</span>
<span class="nx">digits</span> <span class="o">:=</span> <span class="p">[]</span><span class="kt">int</span><span class="p">{}</span>
<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">c</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">twoToThePowerOf1000</span><span class="p">.</span><span class="nx">String</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">digit</span><span class="p">,</span> <span class="nx">_</span> <span class="o">:=</span> <span class="nx">strconv</span><span class="p">.</span><span class="nx">Atoi</span><span class="p">(</span><span class="nb">string</span><span class="p">(</span><span class="nx">c</span><span class="p">))</span>
<span class="nx">digits</span> <span class="p">=</span> <span class="nb">append</span><span class="p">(</span><span class="nx">digits</span><span class="p">,</span> <span class="nx">digit</span><span class="p">)</span>
<span class="p">}</span>
<span class="nx">sum</span> <span class="o">:=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="nx">_</span><span class="p">,</span> <span class="nx">v</span> <span class="o">:=</span> <span class="k">range</span> <span class="nx">digits</span> <span class="p">{</span>
<span class="nx">sum</span> <span class="p">=</span> <span class="nx">sum</span> <span class="o">+</span> <span class="nx">v</span>
<span class="p">}</span>
<span class="nx">fmt</span><span class="p">.</span><span class="nx">Printf</span><span class="p">(</span><span class="s">"%d\n"</span><span class="p">,</span> <span class="nx">sum</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div>
<h2>People & Community</h2>
<p>Go có cộng đồng trên toàn cầu, có forum/slack/IRC để thảo luận, xem tại <a href="https://golang.org/help/#help">help</a> - có cả bằng Tiếng Việt.</p>
<p>Những nhân vật đáng chú ý/follow trong cộng đồng Go gồm các tác giả, core dev, ...</p>
<ul>
<li><a href="https://blog.golang.org/index">Go blog</a></li>
<li><a href="https://en.wikipedia.org/wiki/Rob_Pike">Rob Pike</a></li>
<li><a href="https://dave.cheney.net/category/golang">Dave Cheney</a></li>
<li><a href="https://swtch.com/~rsc/">Russ Cox</a></li>
</ul>
<h2>Kết luận</h2>
<p>Go là một ngôn ngữ lập trình đơn giản và thú vị, với những kiến thức trong bài này, ta đã có thể bắt đầu dùng Go để viết các chương trình không hề đơn giản.
Phần tiếp sẽ trình bày chi tiết về các khái niệm chỉ có trong Go mà không có trong Python như Pointer, sự khác biệt về cách tổ chức package trong Go, declaration & initialization (khai báo và khởi tạo variable), cùng các standard library quan trọng nhất cho một SysAdmin/DevOps.</p>
<h2>References</h2>
<ul>
<li><a href="https://blog.iron.io/how-we-went-from-30-servers-to-2-go/">https://blog.iron.io/how-we-went-from-30-servers-to-2-go/</a></li>
<li><a href="https://blog.iron.io/go-after-2-years-in-production/">https://blog.iron.io/go-after-2-years-in-production/</a></li>
<li><a href="https://blog.golang.org/survey2019-results">https://blog.golang.org/survey2019-results</a></li>
<li><a href="https://github.com/golang/go/wiki/SuccessStories">https://github.com/golang/go/wiki/SuccessStories</a></li>
<li><a href="http://www.catb.org/~esr/reposurgeon/GoNotes.html">Notes on the Go translation of Reposurgeon - from Python</a></li>
<li><a href="https://hn.algolia.com/?q=go">https://hn.algolia.com/?q=go</a></li>
</ul>
<h2>Next</h2>
<p>Đã có phần 2 <a href="http://pp.pymi.vn/article/go12/"><strong>Cứ đi là đến</strong></a>.</p>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>.</p>Viết code dễ đổi, dễ test như thế nào?2020-08-17T00:00:00+07:002020-08-17T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-08-17:/article/repl/<p>Tận dụng tối đa REPL của Python, tính năng mà C, Java, Golang không có</p><p>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.</p>
<p>Khi học Python, việc đầu tiên ta làm là bật <code>python</code> từ terminal, rồi gõ trực
tiếp các dòng code vào đó, enter để thấy kết quả:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">python3</span>
<span class="n">Python</span> <span class="mf">3.6.9</span> <span class="p">(</span><span class="n">default</span><span class="p">,</span> <span class="n">Apr</span> <span class="mi">18</span> <span class="mi">2020</span><span class="p">,</span> <span class="mi">01</span><span class="p">:</span><span class="mi">56</span><span class="p">:</span><span class="mi">04</span><span class="p">)</span>
<span class="p">[</span><span class="n">GCC</span> <span class="mf">8.4.0</span><span class="p">]</span> <span class="n">on</span> <span class="n">linux</span>
<span class="n">Type</span> <span class="s2">"help"</span><span class="p">,</span> <span class="s2">"copyright"</span><span class="p">,</span> <span class="s2">"credits"</span> <span class="ow">or</span> <span class="s2">"license"</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
<span class="o">>>></span> <span class="nb">str</span><span class="p">(</span><span class="mi">21</span> <span class="o">*</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="s2">" is the answer of life."</span>
<span class="s1">'42 is the answer of life.'</span>
</code></pre></div>
<p>Còn khi đi làm, viết code Python? Cũng vậy!</p>
<p>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ó <code>REPL</code>
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.</p>
<p><img alt="xkcd303" src="https://imgs.xkcd.com/comics/compiling.png"></p>
<p>Đây là chế độ <a href="https://docs.python.org/3/tutorial/interpreter.html">"interactive mode"</a>
của Python interpreter, khái niệm này có cái tên khác chung hơn là: REPL.</p>
<p>(Chú ý: Golang có các project như <a href="https://github.com/motemen/gore"><code>gore</code></a> hay
<code>yaegi</code> nhưng đều rất hạn chế so với REPL của các ngôn ngữ kể trên).</p>
<h2>REPL</h2>
<p>REPL - Read Eval Print Loop, là môi trường nhận đầu vào từ người dùng (<code>Read</code>),
chạy input đó (<code>Eval</code>), in kết quả ra màn hình (<code>Print</code>), và cứ tiếp tục vậy
(<code>Loop</code>).</p>
<p>Khái niệm này bắt nguồn từ ngôn ngữ lập trình cổ thứ 2 thế giới: <a href="https://pp.pymi.vn/article/scm1/">LISP</a>.</p>
<p>Việc viết code khi dùng các ngôn ngữ có REPL thường theo các bước:</p>
<ul>
<li>bật REPL lên</li>
<li>gõ code thử cho tới khi thu được kết quả mong muốn</li>
<li>copy code đó vào editor/IDE</li>
</ul>
<h2>Ví dụ</h2>
<p>Đoạn code Python 3 sau sẽ truy cập API của GitHub, lấy các repo của Pymivn về,
lọc ra các repo có > 0 star, sắp xếp giảm dần theo số star,
rồi in ra output ở dạng dễ đọc.</p>
<div class="highlight"><pre><span></span><code><span class="c1"># githubstars.py</span>
<span class="kn">from</span> <span class="nn">urllib.request</span> <span class="kn">import</span> <span class="n">urlopen</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="k">with</span> <span class="n">urlopen</span><span class="p">(</span><span class="s2">"https://api.github.com/users/pymivn/repos"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">repos</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="n">has_stars</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">repo</span> <span class="ow">in</span> <span class="n">repos</span><span class="p">:</span>
<span class="k">if</span> <span class="n">repo</span><span class="p">[</span><span class="s2">"stargazers_count"</span><span class="p">]</span> <span class="o">></span> <span class="mi">0</span><span class="p">:</span>
<span class="n">has_stars</span><span class="o">.</span><span class="n">append</span><span class="p">((</span>
<span class="n">repo</span><span class="p">[</span><span class="s2">"stargazers_count"</span><span class="p">],</span> <span class="n">repo</span><span class="p">[</span><span class="s2">"html_url"</span><span class="p">]</span>
<span class="p">))</span>
<span class="n">has_stars</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">for</span> <span class="n">stars</span><span class="p">,</span> <span class="n">url</span> <span class="ow">in</span> <span class="n">has_stars</span><span class="p">:</span>
<span class="n">output</span> <span class="o">=</span> <span class="s2">"</span><span class="si">{}</span><span class="s2"> - </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">stars</span><span class="p">,</span> <span class="n">url</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>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:</p>
<ul>
<li>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).</li>
<li>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.</li>
<li>Không test từng phần (bước) của đoạn code được.</li>
</ul>
<p>Thay vì vậy, viết lại một phần code như sau</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">urllib.request</span> <span class="kn">import</span> <span class="n">urlopen</span>
<span class="kn">import</span> <span class="nn">json</span>
<span class="k">def</span> <span class="nf">getrepos</span><span class="p">():</span>
<span class="k">with</span> <span class="n">urlopen</span><span class="p">(</span><span class="s2">"https://api.github.com/users/pymivn/repos"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">repos</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="k">return</span> <span class="n">repos</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="k">pass</span>
</code></pre></div>
<p>Lưu vào file <code>github.py</code>, rồi vào terminal, bật <code>python3</code> lên, gõ:</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">github</span>
<span class="o">>>></span> <span class="n">repos</span> <span class="o">=</span> <span class="n">github</span><span class="o">.</span><span class="n">getrepos</span><span class="p">()</span>
<span class="o">>>></span> <span class="nb">type</span><span class="p">(</span><span class="n">repos</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">repos</span><span class="p">)</span>
<span class="p">(</span><span class="o"><</span><span class="k">class</span> <span class="err">'</span><span class="nc">list</span><span class="s1">'>, 23)</span>
<span class="o">>>></span> <span class="n">one</span> <span class="o">=</span> <span class="n">repos</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">one</span><span class="o">.</span><span class="n">keys</span><span class="p">()</span>
<span class="n">dict_keys</span><span class="p">([</span><span class="s1">'id'</span><span class="p">,</span> <span class="s1">'node_id'</span><span class="p">,</span> <span class="s1">'name'</span><span class="p">,</span> <span class="s1">'full_name'</span><span class="p">,</span> <span class="s1">'private'</span><span class="p">,</span> <span class="s1">'owner'</span><span class="p">,</span> <span class="s1">'html_url'</span><span class="p">,</span> <span class="s1">'description'</span><span class="p">,</span> <span class="s1">'fork'</span><span class="p">,</span> <span class="s1">'url'</span><span class="p">,</span> <span class="s1">'forks_url'</span><span class="p">,</span> <span class="s1">'keys_url'</span><span class="p">,</span> <span class="s1">'collaborators_url'</span><span class="p">,</span> <span class="s1">'teams_url'</span><span class="p">,</span> <span class="s1">'hooks_url'</span><span class="p">,</span> <span class="s1">'issue_events_url'</span><span class="p">,</span> <span class="s1">'events_url'</span><span class="p">,</span> <span class="s1">'assignees_url'</span><span class="p">,</span> <span class="s1">'branches_url'</span><span class="p">,</span> <span class="s1">'tags_url'</span><span class="p">,</span> <span class="s1">'blobs_url'</span><span class="p">,</span> <span class="s1">'git_tags_url'</span><span class="p">,</span> <span class="s1">'git_refs_url'</span><span class="p">,</span> <span class="s1">'trees_url'</span><span class="p">,</span> <span class="s1">'statuses_url'</span><span class="p">,</span> <span class="s1">'languages_url'</span><span class="p">,</span> <span class="s1">'stargazers_url'</span><span class="p">,</span> <span class="s1">'contributors_url'</span><span class="p">,</span> <span class="s1">'subscribers_url'</span><span class="p">,</span> <span class="s1">'subscription_url'</span><span class="p">,</span> <span class="s1">'commits_url'</span><span class="p">,</span> <span class="s1">'git_commits_url'</span><span class="p">,</span> <span class="s1">'comments_url'</span><span class="p">,</span> <span class="s1">'issue_comment_url'</span><span class="p">,</span> <span class="s1">'contents_url'</span><span class="p">,</span> <span class="s1">'compare_url'</span><span class="p">,</span> <span class="s1">'merges_url'</span><span class="p">,</span> <span class="s1">'archive_url'</span><span class="p">,</span> <span class="s1">'downloads_url'</span><span class="p">,</span> <span class="s1">'issues_url'</span><span class="p">,</span> <span class="s1">'pulls_url'</span><span class="p">,</span> <span class="s1">'milestones_url'</span><span class="p">,</span> <span class="s1">'notifications_url'</span><span class="p">,</span> <span class="s1">'labels_url'</span><span class="p">,</span> <span class="s1">'releases_url'</span><span class="p">,</span> <span class="s1">'deployments_url'</span><span class="p">,</span> <span class="s1">'created_at'</span><span class="p">,</span> <span class="s1">'updated_at'</span><span class="p">,</span> <span class="s1">'pushed_at'</span><span class="p">,</span> <span class="s1">'git_url'</span><span class="p">,</span> <span class="s1">'ssh_url'</span><span class="p">,</span> <span class="s1">'clone_url'</span><span class="p">,</span> <span class="s1">'svn_url'</span><span class="p">,</span> <span class="s1">'homepage'</span><span class="p">,</span> <span class="s1">'size'</span><span class="p">,</span> <span class="s1">'stargazers_count'</span><span class="p">,</span> <span class="s1">'watchers_count'</span><span class="p">,</span> <span class="s1">'language'</span><span class="p">,</span> <span class="s1">'has_issues'</span><span class="p">,</span> <span class="s1">'has_projects'</span><span class="p">,</span> <span class="s1">'has_downloads'</span><span class="p">,</span> <span class="s1">'has_wiki'</span><span class="p">,</span> <span class="s1">'has_pages'</span><span class="p">,</span> <span class="s1">'forks_count'</span><span class="p">,</span> <span class="s1">'mirror_url'</span><span class="p">,</span> <span class="s1">'archived'</span><span class="p">,</span> <span class="s1">'disabled'</span><span class="p">,</span> <span class="s1">'open_issues_count'</span><span class="p">,</span> <span class="s1">'license'</span><span class="p">,</span> <span class="s1">'forks'</span><span class="p">,</span> <span class="s1">'open_issues'</span><span class="p">,</span> <span class="s1">'watchers'</span><span class="p">,</span> <span class="s1">'default_branch'</span><span class="p">])</span>
<span class="o">>>></span> <span class="n">one</span><span class="p">[</span><span class="s1">'stargazers_count'</span><span class="p">]</span>
<span class="mi">0</span>
<span class="o">>>></span> <span class="n">has_stars</span> <span class="o">=</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">repos</span> <span class="k">if</span> <span class="n">p</span><span class="p">[</span><span class="s1">'stargazers_count'</span><span class="p">]</span> <span class="o">></span> <span class="mi">0</span><span class="p">]</span>
<span class="o">>>></span> <span class="nb">len</span><span class="p">(</span><span class="n">has_stars</span><span class="p">)</span>
<span class="mi">8</span>
<span class="o">>>></span> <span class="n">stars_urls</span> <span class="o">=</span> <span class="p">[(</span><span class="n">p</span><span class="p">[</span><span class="s1">'stargazers_count'</span><span class="p">],</span> <span class="n">p</span><span class="p">[</span><span class="s1">'html_url'</span><span class="p">])</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">has_stars</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">stars_urls</span>
<span class="p">[(</span><span class="mi">7</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/awesome'</span><span class="p">),</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/cpuisfast'</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/hoidap-python'</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/lekhome'</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/math-stats-ml'</span><span class="p">),</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/people'</span><span class="p">),</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/pyjobs_crawlers'</span><span class="p">),</span> <span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/Python_Hanoi_Meetup'</span><span class="p">)]</span>
<span class="o">>>></span> <span class="n">stars_urls</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">fmt</span> <span class="o">=</span> <span class="s2">"</span><span class="si">{}</span><span class="s2"> - </span><span class="si">{}</span><span class="s2">"</span>
<span class="o">>>></span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">stars_urls</span><span class="p">:</span>
<span class="o">...</span> <span class="nb">print</span><span class="p">(</span><span class="n">fmt</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="o">*</span><span class="n">i</span><span class="p">))</span>
<span class="o">...</span>
<span class="mi">7</span> <span class="o">-</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">pymivn</span><span class="o">/</span><span class="n">awesome</span>
<span class="mi">4</span> <span class="o">-</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">pymivn</span><span class="o">/</span><span class="n">math</span><span class="o">-</span><span class="n">stats</span><span class="o">-</span><span class="n">ml</span>
<span class="mi">4</span> <span class="o">-</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">pymivn</span><span class="o">/</span><span class="n">Python_Hanoi_Meetup</span>
<span class="mi">3</span> <span class="o">-</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">pymivn</span><span class="o">/</span><span class="n">people</span>
<span class="mi">2</span> <span class="o">-</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">pymivn</span><span class="o">/</span><span class="n">cpuisfast</span>
<span class="mi">1</span> <span class="o">-</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">pymivn</span><span class="o">/</span><span class="n">pyjobs_crawlers</span>
<span class="mi">1</span> <span class="o">-</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">pymivn</span><span class="o">/</span><span class="n">lekhome</span>
<span class="mi">1</span> <span class="o">-</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">pymivn</span><span class="o">/</span><span class="n">hoidap</span><span class="o">-</span><span class="n">python</span>
</code></pre></div>
<p>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:</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">json</span>
<span class="kn">from</span> <span class="nn">urllib.request</span> <span class="kn">import</span> <span class="n">urlopen</span>
<span class="k">def</span> <span class="nf">getrepos</span><span class="p">():</span>
<span class="k">with</span> <span class="n">urlopen</span><span class="p">(</span><span class="s2">"https://api.github.com/users/pymivn/repos"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">repos</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="k">return</span> <span class="n">repos</span>
<span class="k">def</span> <span class="nf">has_stars</span><span class="p">(</span><span class="n">repo</span><span class="p">):</span>
<span class="k">return</span> <span class="n">repo</span><span class="p">[</span><span class="s2">"stargazers_count"</span><span class="p">]</span> <span class="o">></span> <span class="mi">0</span>
<span class="k">def</span> <span class="nf">filter_repos_have_stars</span><span class="p">(</span><span class="n">repos</span><span class="p">):</span>
<span class="k">return</span> <span class="p">[</span><span class="n">p</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">repos</span> <span class="k">if</span> <span class="n">has_stars</span><span class="p">(</span><span class="n">p</span><span class="p">)]</span>
<span class="k">def</span> <span class="nf">get_star_url</span><span class="p">(</span><span class="n">p</span><span class="p">):</span>
<span class="k">return</span> <span class="p">(</span><span class="n">p</span><span class="p">[</span><span class="s2">"stargazers_count"</span><span class="p">],</span> <span class="n">p</span><span class="p">[</span><span class="s2">"html_url"</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">repos</span> <span class="o">=</span> <span class="n">getrepos</span><span class="p">()</span>
<span class="n">repos_have_stars</span> <span class="o">=</span> <span class="n">filter_repos_have_stars</span><span class="p">(</span><span class="n">repos</span><span class="p">)</span>
<span class="n">stars_urls</span> <span class="o">=</span> <span class="p">[</span><span class="n">get_star_url</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">repos_have_stars</span><span class="p">]</span>
<span class="n">stars_urls</span><span class="o">.</span><span class="n">sort</span><span class="p">(</span><span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">fmt</span> <span class="o">=</span> <span class="s2">"</span><span class="si">{}</span><span class="s2"> - </span><span class="si">{}</span><span class="s2">"</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">stars_urls</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">fmt</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="o">*</span><span class="n">i</span><span class="p">))</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s2">"__main__"</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>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.</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">ipython</span>
<span class="n">Python</span> <span class="mf">3.6.9</span> <span class="p">(</span><span class="n">default</span><span class="p">,</span> <span class="n">Jul</span> <span class="mi">17</span> <span class="mi">2020</span><span class="p">,</span> <span class="mi">12</span><span class="p">:</span><span class="mi">50</span><span class="p">:</span><span class="mi">27</span><span class="p">)</span>
<span class="n">Type</span> <span class="s1">'copyright'</span><span class="p">,</span> <span class="s1">'credits'</span> <span class="ow">or</span> <span class="s1">'license'</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span>
<span class="n">IPython</span> <span class="mf">7.9.0</span> <span class="o">--</span> <span class="n">An</span> <span class="n">enhanced</span> <span class="n">Interactive</span> <span class="n">Python</span><span class="o">.</span> <span class="n">Type</span> <span class="s1">'?'</span> <span class="k">for</span> <span class="n">help</span><span class="o">.</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="kn">import</span> <span class="nn">github</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="n">repos</span> <span class="o">=</span> <span class="n">github</span><span class="o">.</span><span class="n">getrepos</span><span class="p">()</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">3</span><span class="p">]:</span> <span class="n">have_stars</span> <span class="o">=</span> <span class="n">github</span><span class="o">.</span><span class="n">filter_repos_have_stars</span><span class="p">(</span><span class="n">repos</span><span class="p">)</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">4</span><span class="p">]:</span> <span class="nb">len</span><span class="p">(</span><span class="n">have_stars</span><span class="p">)</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">4</span><span class="p">]:</span> <span class="mi">8</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">5</span><span class="p">]:</span> <span class="p">[</span><span class="n">github</span><span class="o">.</span><span class="n">get_star_url</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">have_stars</span><span class="p">]</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">5</span><span class="p">]:</span>
<span class="p">[(</span><span class="mi">7</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/awesome'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/cpuisfast'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/hoidap-python'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/lekhome'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/math-stats-ml'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/people'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/pyjobs_crawlers'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/Python_Hanoi_Meetup'</span><span class="p">)]</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">8</span><span class="p">]:</span> <span class="nb">sorted</span><span class="p">([</span><span class="n">github</span><span class="o">.</span><span class="n">get_star_url</span><span class="p">(</span><span class="n">p</span><span class="p">)</span> <span class="k">for</span> <span class="n">p</span> <span class="ow">in</span> <span class="n">have_stars</span><span class="p">],</span> <span class="n">reverse</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">8</span><span class="p">]:</span>
<span class="p">[(</span><span class="mi">7</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/awesome'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/math-stats-ml'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">4</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/Python_Hanoi_Meetup'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/people'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/cpuisfast'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/pyjobs_crawlers'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/lekhome'</span><span class="p">),</span>
<span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'https://github.com/pymivn/hoidap-python'</span><span class="p">)]</span>
</code></pre></div>
<h2>Dev với IPython</h2>
<p>IPython (<code>pip install ipython</code>) cung cấp thêm các tính năng giúp cách code này
hiệu quả hơn.</p>
<p>IPython có màu mè, auto-indent tự thụt sau for/if giúp gõ nhanh hơn.</p>
<p>Magic command <code>%hist</code> 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.</p>
<p>Magic command <code>%edit</code> 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.</p>
<p>Ví dụ này gõ <code>%edit</code> lần đầu định nghĩa list <code>ns</code>, rồi gõ <code>%edit</code> lần 2 để
print ra list ns định nghĩa trước đó:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">ipython</span>
<span class="n">Python</span> <span class="mf">3.6.9</span> <span class="p">(</span><span class="n">default</span><span class="p">,</span> <span class="n">Apr</span> <span class="mi">18</span> <span class="mi">2020</span><span class="p">,</span> <span class="mi">01</span><span class="p">:</span><span class="mi">56</span><span class="p">:</span><span class="mi">04</span><span class="p">)</span>
<span class="n">Type</span> <span class="s1">'copyright'</span><span class="p">,</span> <span class="s1">'credits'</span> <span class="ow">or</span> <span class="s1">'license'</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span>
<span class="n">IPython</span> <span class="mf">7.9.0</span> <span class="o">--</span> <span class="n">An</span> <span class="n">enhanced</span> <span class="n">Interactive</span> <span class="n">Python</span><span class="o">.</span> <span class="n">Type</span> <span class="s1">'?'</span> <span class="k">for</span> <span class="n">help</span><span class="o">.</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="o">%</span><span class="n">edit</span>
<span class="n">IPython</span> <span class="n">will</span> <span class="n">make</span> <span class="n">a</span> <span class="n">temporary</span> <span class="n">file</span> <span class="n">named</span><span class="p">:</span> <span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">ipython_edit_2v90rimj</span><span class="o">/</span><span class="n">ipython_edit_wf_rn_nc</span><span class="o">.</span><span class="n">py</span>
<span class="n">Editing</span><span class="o">...</span> <span class="n">done</span><span class="o">.</span> <span class="n">Executing</span> <span class="n">edited</span> <span class="n">code</span><span class="o">...</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="s1">'</span><span class="se">\n</span><span class="s1">ns = [1,2,3,4]</span><span class="se">\n</span><span class="s1">'</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="n">ns</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">3</span><span class="p">]:</span> <span class="o">%</span><span class="n">edit</span>
<span class="n">IPython</span> <span class="n">will</span> <span class="n">make</span> <span class="n">a</span> <span class="n">temporary</span> <span class="n">file</span> <span class="n">named</span><span class="p">:</span> <span class="o">/</span><span class="n">tmp</span><span class="o">/</span><span class="n">ipython_edit_qb43pml6</span><span class="o">/</span><span class="n">ipython_edit_tph7_5x6</span><span class="o">.</span><span class="n">py</span>
<span class="n">Editing</span><span class="o">...</span> <span class="n">done</span><span class="o">.</span> <span class="n">Executing</span> <span class="n">edited</span> <span class="n">code</span><span class="o">...</span>
<span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">3</span><span class="p">]:</span> <span class="s1">'print(ns)</span><span class="se">\n</span><span class="s1">'</span>
</code></pre></div>
<h3>Đổi editor</h3>
<p>ra shell, gõ <code>echo $EDITOR</code> xem đang đặt
là gì, thay bằng câu lệnh mở editor mình muốn, ví dụ</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">export</span> <span class="nv">EDITOR</span><span class="o">=</span>nano
$ ipython
</code></pre></div>
<h3>Chạy file rồi bật REPL</h3>
<p>Python hay IPython đều hỗ trợ <a href="https://pp.pymi.vn/article/pythoni/">option
<code>-i</code></a>, sau khi chạy với 1 file code
sẽ tự động vào chế độ interactive mode</p>
<h2>Jupyter</h2>
<p>Code trên Jupyter (<code>pip install jupyter</code>) cũng cho khả năng linh hoạt tương tự.
Code xong File > Save As Python file.</p>
<h2>Unittest</h2>
<p>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á.</p>
<p>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 <code>pytest</code> (<code>pip install pytest</code>) như sau:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># test_github.py</span>
<span class="kn">import</span> <span class="nn">github</span>
<span class="k">def</span> <span class="nf">test</span><span class="p">():</span>
<span class="n">bad</span> <span class="o">=</span> <span class="p">{</span><span class="s2">"stargazers_count"</span><span class="p">:</span> <span class="mi">0</span><span class="p">,</span> <span class="s2">"html_url"</span><span class="p">:</span> <span class="s2">"bad_repo"</span><span class="p">}</span>
<span class="n">good</span> <span class="o">=</span> <span class="p">{</span>
<span class="s2">"stargazers_count"</span><span class="p">:</span> <span class="mi">69</span><span class="p">,</span>
<span class="s2">"html_url"</span><span class="p">:</span> <span class="s2">"https://github.com/pymivn/awesome"</span><span class="p">,</span>
<span class="s2">"blah"</span><span class="p">:</span> <span class="s2">"blo"</span><span class="p">,</span>
<span class="p">}</span>
<span class="n">sample_repos</span> <span class="o">=</span> <span class="p">[</span><span class="n">bad</span><span class="p">,</span> <span class="n">good</span><span class="p">]</span>
<span class="k">assert</span> <span class="n">github</span><span class="o">.</span><span class="n">filter_repos_have_stars</span><span class="p">(</span><span class="n">sample_repos</span><span class="p">)</span> <span class="o">==</span> <span class="p">[</span><span class="n">good</span><span class="p">]</span>
<span class="k">assert</span> <span class="n">github</span><span class="o">.</span><span class="n">get_star_url</span><span class="p">(</span><span class="n">good</span><span class="p">)</span> <span class="o">==</span> <span class="p">(</span>
<span class="n">good</span><span class="p">[</span><span class="s2">"stargazers_count"</span><span class="p">],</span>
<span class="n">good</span><span class="p">[</span><span class="s2">"html_url"</span><span class="p">],</span>
<span class="p">)</span>
<span class="k">assert</span> <span class="n">github</span><span class="o">.</span><span class="n">has_stars</span><span class="p">(</span><span class="n">bad</span><span class="p">)</span> <span class="ow">is</span> <span class="kc">False</span>
<span class="k">assert</span> <span class="n">github</span><span class="o">.</span><span class="n">has_stars</span><span class="p">(</span><span class="n">good</span><span class="p">)</span> <span class="ow">is</span> <span class="kc">True</span>
</code></pre></div>
<p>Viết code bằng REPL hay bằng <a href="https://en.wikipedia.org/wiki/Test-driven_development">unittest
TDD</a> đều mang tới một
kết quả chung: code dễ sửa, dễ test.</p>
<p>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 <del>tương đương như</del> hơn nhiều unittest ở
các ngôn ngữ khác.</p>
<h2>Hành động của chúng ta</h2>
<p>Cài ngay IPython, Jupyter rồi bật lên mỗi khi muốn code Python.</p>
<h2>Kết luận</h2>
<p>Đừ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.</p>Phỏng vấn2020-04-10T00:00:00+07:002020-04-10T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-04-10:/article/phongvan/<p>phỏng vấn thì chuẩn bị gì? làm thế nào để "deal" lương? học xong xin việc thế nào?</p><p>Phỏng vấn là hai từ mang yếu tố quyết định, nhưng ít khi được nhắc đến.
Nó thường bị xem như sự may mắn, hay do "giỏi", hay quan hệ tốt ...
Bài viết này giải đáp các thắc mắc thầm kín khi đi phỏng vấn xin việc, đặc biệt
dành cho những người kiếm công việc lập trình đầu tiên.</p>
<h2>Phỏng vấn là gì?</h2>
<p>Khi muốn đi làm, một lập trình viên sẽ liên hệ với một công ty để "xin việc" nhằm
có một công việc, được trả lương tại công ty đó, ở đây ví dụ là PAMA corp.</p>
<p>PAMA corp sẽ cử một hoặc một nhóm người thực hiện việc "kiểm tra đầu vào". Sau
quá trình kiểm tra, nếu phía PAMA corp đồng ý, phía lập trình viên đồng ý,
sẽ ký một hợp đồng lao động, rồi lập trình viên đi làm, và được trả lương.</p>
<h2>Nộp "đơn xin việc" thế nào?</h2>
<p>Thông thường, tức không phải các trường hợp ngoại lệ như bị "săn" đón đi làm,
hay có quan hệ được "giới thiệu" vào làm, thì:</p>
<ul>
<li>lập trình viên sẽ lên các trang tuyển dụng/tìm việc (<a href="https://jobs.pymi.vn/">như PyJobs</a>)/trang chủ của công ty cụ thể để tìm công việc mong muốn,</li>
<li>hoặc các "head hunter" chuyên đi săn ứng viên hay HR (human resource - nhân
sự) của các công ty chủ động liên hệ, mời (or gạ) nộp hồ sơ phỏng vấn. Chú ý
đây là một nghề hái ra tiền, nên đừng shock nếu được mời chào đon đả.</li>
</ul>
<h2>Quy trình phỏng vấn ra sao?</h2>
<p>Thông thường sẽ có:</p>
<ul>
<li>HH/HR gọi điện, thống nhất về Job description (JD - yêu cầu công việc), có
thể có cả mức lương thưởng/chính sách. Nếu không có, hãy hỏi kỹ, cụ thể để
khỏi mất thời gian. Bạn sẽ không muốn tốn thời gian 4 tuần đi qua 8 vòng phỏng
vấn để biết mức thu nhập tối đa công ty sẽ trả cho vị trí đó là 1.500 USD, trong
khi thu nhập hiện tại của bạn đang là 3000 USD.
HR sẽ chốt quy trình phỏng vấn (mấy vòng), thời gian phỏng vấn vòng 1.</li>
<li>Vòng 0: các công ty có thể có bài test online, thường là code 2-3 bài thuật
toán trong vòng 1 tiếng trên 1 trang code online như hackerank.com.
Hoặc cũng có thể cho 1 bài "test" về nhà làm 1 sản phẩm gì đó rồi nộp sau 1
tuần.</li>
<li>Nếu có vòng 0, và nếu đạt yêu cầu, HR sẽ email/ gọi điện hẹn phỏng vấn vòng 1.</li>
<li>Vòng 1, có thể là cả vòng 2, hay thậm chí vòng N, là việc gặp và trả lời các
câu hỏi do đại diện phía công ty đưa ra.</li>
<li>Nếu N vòng này thành công, sẽ tới vòng cuối cùng: "deal" lương. Dựa vào kết
quả N vòng trước mà công ty quyết định có tuyển lập trinh viên không, và
trả thù lao bao nhiêu.</li>
<li>Hai bên nếu đồng ý sẽ ký giấy tờ, và hẹn ngày đi làm. Với người đang đi làm
ở công ty khác, sẽ thỏa thuận sau thời gian X ngày (thường là 30-45 ngày)
sẽ đi làm do đó là thời gian cần thiết để xin nghỉ, chấm dứt hợp đồng với
công ty cũ.</li>
</ul>
<h2>Đơn xin việc - Resume - CV</h2>
<p>Thường là 1 file PDF (để đọc được trên mọi hệ điều hành),
bằng tiếng Anh (để tỏ ra chuyên nghiệp), có link GitHub đến các project cá
nhân thay vì đính kèm file RAR.</p>
<p>Xem mẫu CV học viên mới tốt nghiệp <a href="https://pymi.vn">pymivn</a>
tại <a href="https://bit.ly/pymicv">đây</a>.</p>
<p>Tránh các mẫu CV có "thanh năng lượng" đánh giá 3 sao 5 sao. Really? bạn
có chắc mình đạt 3 sao? so với ai? So với Guido van Rossum?
Để 4 5 sao nhiều khi phản tác dụng khiến cho phía bên kia tìm cách mà dìm bạn
xuống, khiêm tốn là một (đức) tính được yêu thích (trước) khi đi làm.</p>
<h2>Các vấn đề bất cập của quy trình phỏng vấn</h2>
<ul>
<li>Không có chuẩn, không có khuôn mẫu, mỗi chỗ một kiểu. Có khi lý do
không ai viết bài về chuyện phỏng vấn bởi nó chả bao giờ giống nhau cả.</li>
<li>Vậy nên những thứ viết trong bài này, hãy xem là tương đối, bởi tác giả
không muốn thêm chữ "một cách tương đối" cuối mỗi câu, hay "đôi khi"/"hầu
hết" ở mỗi đầu câu.</li>
</ul>
<h2>Những bí quyết phỏng vấn</h2>
<ul>
<li>HIỂU rằng ký hợp đồng lao động và đi làm cho một công ty, đó là một thỏa
thuận <strong>kinh tế</strong> giữa 2 bên, thuận mua - vừa bán, như đi mua thịt mua rau.
Phía công ty dùng tiền để mua - phía lập trình viên bán sức lao động. Không
phải chuyện xin cho, ơn huệ, nợ nần gì ai cả. Không có công ty làm việc không
vì lợi nhuận, nhận vào làm vì <a href="https://www.youtube.com/watch?v=iJKV5miglAg">thương bạn như thương cây bàng
non</a>. Cũng không có chuyện "anh
em như thể người nhà, bạn bè đồng nghiệp như là người thân". No, please.
Bạn sẽ bị thay thế bất cứ lúc nào nếu không còn có lợi cho công ty.</li>
<li>Không để lộ mức lương hiện tại, bằng mọi giá. Vì lý do gì công ty PAMA corp
cần biết mức lương cũ của bạn? lý do duy nhất là để ép nó xuống. Nếu bạn nói
lương hiện tại 10 triệu, công ty hoàn toàn tự tin trả bạn 11 triệu. Nhưng nếu
không biết, họ chỉ có thể trả thấp hơn 1 chút mức bạn yêu cầu. Ở Mỹ, có hẳn
<a href="https://www.sfgate.com/business/networth/article/New-law-bans-California-employers-from-asking-12274431.php">luật cấm các công ty hỏi lịch sử lương ứng viên</a>. Ở Việt Nam không có luật này, nhưng nếu bị hỏi, hãy nói đã kỹ thỏa thuận
không tiết lộ <a href="https://en.wikipedia.org/wiki/Non-disclosure_agreement">NDA</a>
của công ty cũ / hiện tại. HR sẽ hỏi vòng rằng "mức mong đợi của
anh là bao nhiêu?", lúc đó cứ thoải mái nói mức bạn mong muốn, hay cộng 7-10
triệu vào mức hiện tại, không mấy ai nhảy việc để nhận thêm 1 triệu mỗi tháng
cả.</li>
<li>Hỏi rõ mức thu nhập cụ thể, kể cả khoảng (range - từ X đến Y). Rất nhiều công
ty để lương theo kiểu "thỏa thuận", hay "có thể thương lượng", hay HR cương
quyết không nói mà đòi phải qua vòng phỏng vấn. Cũng nên chú ý rằng, nếu
công ty nói là max 2000 USD hay upto 2000 USD, thường nghĩa là họ chỉ trả max
1 nghìn mốt.</li>
<li>Hỏi rõ lương làm Gross hay Net, search internet để hiểu rõ sự khác nhau này.</li>
<li>Nhận 80% lương thử việc là điều thường thấy, nhưng không phải bắt buộc.
Ít khi thấy ở các vị trí câp cao vài nghìn USD.</li>
<li>Không phải "giỏi" kỹ thuật hơn thì lương cao hơn. Có chăng lý do lương A cao
hơn B dù trình độ "ngang nhau" là bởi A giỏi thương lượng/đàm phán/thỏa
thuận hơn B.</li>
<li>Các câu hỏi mang tính chất dìm hàng như bằng cấp (đặc biệt khi bạn không tốt
nghiệp đại học ngành công nghệ thông tin) nhằm làm căn cứ để giảm lương của bạn.
Chỉ một số các công ty nhà nước/ngân hàng mới thực sự đòi hỏi bằng cấp. Hay
khi phải cân nhấc 2 ứng viên ngang ngửa, bằng mới được lôi ra.</li>
<li>Mức lương tối thiểu cho một lập trình viên Python biết làm web Flask hay
dùng pandas hay viết crawler (hay tốt nghiệp <a href="https://pymi.vn">Pymi.vn</a>)
là 8-10 triệu VND tại năm 2019. Sau 1 năm kinh nghiệm,
con số này có thể gấp đôi, hoặc hơn.</li>
<li>Các yêu cầu ghi trong JD là yêu cầu lý tưởng, chứ không phải điều kiện tiên
quyết. Như 2 năm kinh nghiệm hay bằng đại học, hay 10 dòng yêu cầu khác.</li>
<li>Trượt phỏng vấn 10 công ty: chúc mừng, bạn đã đánh 10 con quái và lên level 2.
Thất bại là một phần tất yếu của thành công.
Quan trọng là về biết rút kinh nghiệm, học những câu trả lời sai để lần sau
còn làm đúng. Rồi bạn sẽ ngạc nhiên khi nhiều công ty/nhiều vòng phỏng vấn
có "kho" câu hỏi trùng nhau.</li>
</ul>
<h2>Kỹ thuật</h2>
<p>Bên trên toàn nói chuyện thoả thuận, tiền, thế còn các câu hỏi kỹ thuật thì sao?
các thuật toán cao siêu thì thế nào???</p>
<p>Thực ra những vấn đề kỹ thuật không mang nhiều tính chất quyết định. Đúng như
táo quân nói, những thứ khác quan trọng hơn:</p>
<ul>
<li>quan hệ: rất nhiều người đi làm nhờ quan hệ, quan hệ ở đây không có nghĩa xấu
như trong táo quân. Một lập trình viên giỏi chưa chắc có việc nhanh bằng
một thanh niên biết code có nhiều môi quen biết, biết chỗ nào đang cần người.
Nhiều mối ngon cũng đến do ông A chỉ biết thằng B làm Python, chả biết thằng
nào khác cả. Lên mạng đi tuyển lại phải đau đầu với chuyện tuyển dụng/ cạnh
tranh với hàng trăm công ty khác khi dev Python giờ hot như cồn và khẩu trang.</li>
<li>may mắn: ôn đúng bài, bị hỏi toàn cái biết, hay đẹp trai cũng là một dạng may
mắn.</li>
</ul>
<h3>Những điều không phải là thất bại</h3>
<ul>
<li>Các công ty phỏng vấn Python nhưng lại toàn hỏi C, Java, PHP... Nếu là một
lập trình viên Python / backend, thì khi phỏng vấn tôi cần trả lời các vấn
đề về Python, hay SQL, hay thậm chí tool làm việc như git, docker. Nhưng
chẳng có lý do gì lại hỏi C array so sánh với Python list, hay Java/C#
design pattern cho Python cả. Khi gặp những công ty hỏi kiểu này, bạn không trả
lời được thì đừng trách mình "dốt". Nếu có được hỏi lại, hãy hỏi họ giá trị gì
mang lại khi họ hỏi những câu đó với 1 lập trình viên Python. Hoặc bạn sẽ
biết phía bên kia không biết gì về Python (không hiếm với các công ty outsource),
hoặc sẽ học được điều gì đó.</li>
<li>Trượt các câu hỏi thuật toán/IQ: các câu hỏi này rất đa dạng, trừ khi bạn
dành đủ 6 tháng 2 năm cày luyện Cracking Coding Interview để thi vào Google,
cày leetcode... thì đừng mong trả lời được mọi câu hỏi thuật toán lắt léo.
Và nên nhớ, công ty bạn đang nộp vào, không phải là Google.
Quy trình phỏng vấn dùng các câu hỏi thuật toán <a href="https://news.ycombinator.com/item?id=22331804">hiện được xem như một quy
trình nhiều lỗ hổng</a>,
bởi nó không phải 1 cách tốt để dánh giá ứng viên.</li>
<li>Trượt các câu hỏi lý thuyết trong trường đại học: bên tuyển dụng thường hay hỏi
kể tên các thuật toán sort bạn biết: bubble sort, quick sort, merge sort,
heapsort, insert sort, hay cả <a href="https://www.familug.org/2014/12/algorithm-sleep-sort.html">sleep sort</a>
thế nhưng chưa chắc họ đã biết tên thuật toán sort trong Python (list.sort)
tên là gì? (it's Tim sort) và khả năng cao khác, là họ không giải thích được khi
nào bạn phải dùng các thuật toán trường học trên thay vì Tim sort có sẵn
trong Python.</li>
</ul>
<h2>Fun fact</h2>
<ul>
<li>Có những công ty thực hiện đến hơn 10 cuộc phỏng vấn (phỏng vấn lần lượt từng
người trong team), kéo dài vài tháng.</li>
<li>Có nhiều công ty "top", "hot", "đỉnh", "chỉ tuyển người giỏi nhất", nhưng trả
mức thù lao mà chỗ nào cũng trả được.</li>
</ul>
<h2>Dặn dò</h2>
<p>Nhớ làm thành thạo bài tập của <a href="https://pymi.vn">pymi</a> và đọc <a href="https://faq.pymi.vn/">PYMI interview
FAQs</a>.</p>
<h2>Kết luận</h2>
<p>Phỏng vấn là một kỹ năng, giống như code, để giỏi, cần phải rèn luyện nhiều.
Phỏng vấn là một cuộc đấu trí, thậm chí chẳng có câu hỏi kỹ thuật chi tiết nào
được đưa ra mà ứng viên vẫn hoàn thành xuất sắc cuộc phỏng vấn.</p>
<p>Nếu lần đầu đi nộp CV, hãy rải thảm tất cả các công ty, hãy đi phỏng vấn thật
nhiều, trượt thật nhiều, rút kinh nghiệm, rồi sau 10 lần phỏng vấn, kiểu gì
chả trúng 1. Còn nếu đã đi làm, trừ khi ấm chỗ leader/trưởng phòng/giám đốc/CTO/
CBE thì mỗi mùa xuân hạ thu đông cũng nên đi phỏng vấn 1 lần để biết trình độ
mình ở đâu, và đáng giá được bao nhiêu nào.</p>Dùng Python như các CLI tool2020-03-24T00:00:00+07:002020-03-24T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-03-24:/article/pycli/<p>Python vốn được dùng để viết script, code trong file nhưng vẫn tỏa sáng khi gõ trong command line</p><p>Những "hacker" trên dòng lệnh luôn gõ nhoay nhoáy các <a href="https://www.familug.org/search/label/CLI">"command
line"</a> để xử lý text: <a href="https://www.familug.org/search/label/CCGU">grep, cut,
uniq, sort,...</a> hay đôi khi chơi hẳn
sed hoặc <a href="https://pp.pymi.vn/article/awk/">AWK</a>, thậm chí Perl5.</p>
<p>Thời xưa, Perl5 vốn là công cụ số một của các
<a href="https://www.familug.org/2015/01/e-tro-thanh-linux-sysadmin.html">SysAdmin</a>,
khi mà Python vẫn chưa phổ biến do quá sạch đẹp nhưng cũng hơi "dài dòng" (so
với Perl). Muốn làm gì với Python cũng phải viết ra 1 file, rồi chmod
a+x rồi mới chạy được. Perl thì có cả ngàn phép biến hóa chỉ bằng 1 dòng, gọi
là <a href="https://duckduckgo.com/?q=perl+one-liners&t=ffab&ia=web">one-liner</a> hay
trên <a href="https://en.wikipedia.org/wiki/One-liner_program#Perl">Wikipedia</a>:</p>
<div class="highlight"><pre><span></span><code><span class="n">perl</span> <span class="o">-</span><span class="n">lne</span> <span class="s">'print if $_ eq reverse'</span> <span class="sr">/usr/s</span><span class="n">hare</span><span class="sr">/dict/</span><span class="n">american</span><span class="o">-</span><span class="n">english</span>
</code></pre></div>
<p>1 dòng trên để tìm ra các từ "palindrome' (ngược xuôi như nhau).</p>
<h2>Python</h2>
<h3>Python 1-liner</h3>
<p>Python 1-liner vốn không ngắn như mong đợi, do code Python
nhấn mạnh vào sự rõ ràng dễ đọc, nên không có các ký tự bí hiểm <code>$_</code> như Perl. Viết
Python 1 dòng dùng option <code>-c</code> như sau:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">python3</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'import math; print(math.sqrt(2**1000))'</span>
<span class="mf">3.273390607896142e+150</span>
</code></pre></div>
<p>Các dòng không cần phải xuống dòng mà dùng dấu <code>;</code> để ngăn cách. Nhưng viết
<code>for</code> hay <code>if</code> thì ... hơi khó.</p>
<h3>Python nhiều dòng</h3>
<p>Cách này đơn giản hơn, viêt code thành nhiều dòng, ý hệt như code trong file. Dùng dấu single quote <code>'</code> rồi enter để gõ code, sau đó kết thúc bằng dầu single quote <code>'</code>.</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">python3</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'</span>
<span class="o">></span> <span class="nb">sum</span> <span class="o">=</span> <span class="mi">0</span>
<span class="o">></span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span>
<span class="o">></span> <span class="nb">sum</span> <span class="o">+=</span> <span class="n">i</span>
<span class="o">></span> <span class="nb">print</span><span class="p">(</span><span class="nb">sum</span><span class="p">)</span>
<span class="o">></span> <span class="s1">'</span>
<span class="mi">45</span>
</code></pre></div>
<p>Ví dụ sau lấy ra Shell của user <code>root</code> ghi trong file /etc/passwd, viết HOA:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">cat</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">passwd</span> <span class="o">|</span> <span class="n">python</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'</span>
<span class="o">></span> <span class="kn">import</span> <span class="nn">sys</span>
<span class="o">></span> <span class="nb">print</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">":"</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span>
<span class="o">></span> <span class="s1">'</span>
<span class="o">/</span><span class="n">BIN</span><span class="o">/</span><span class="n">BASH</span>
</code></pre></div>
<p>hay đếm số ký tự kết quả trên bằng lệnh <code>wc</code>:</p>
<div class="highlight"><pre><span></span><code> <span class="err">$</span> <span class="n">cat</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">passwd</span> <span class="o">|</span> <span class="n">python3</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="nb">print</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">":"</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span>
<span class="s1">' | wc -c</span>
<span class="mi">11</span>
</code></pre></div>
<p>Không phải 1 dòng, nhưng đẹp hơn 1 dòng, và hoàn toàn hợp lý.</p>
<p>Ví dụ khác để in JSON đẹp:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">echo</span> <span class="s1">'{"name": "PyMIers", "year": 2020}'</span> <span class="o">|</span> <span class="n">python3</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'</span>
<span class="kn">import</span> <span class="nn">json</span><span class="o">,</span> <span class="nn">sys</span>
<span class="nb">print</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">),</span> <span class="n">indent</span><span class="o">=</span><span class="mi">4</span><span class="p">))</span><span class="s1">'</span>
<span class="p">{</span>
<span class="s2">"name"</span><span class="p">:</span> <span class="s2">"PyMIers"</span><span class="p">,</span>
<span class="s2">"year"</span><span class="p">:</span> <span class="mi">2020</span>
<span class="p">}</span>
</code></pre></div>
<p>Hay dùng module có sẵn:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">echo</span> <span class="s1">'{"name": "PyMIers", "year": 2020}'</span> <span class="o">|</span> <span class="n">python3</span> <span class="o">-</span><span class="n">m</span> <span class="n">json</span><span class="o">.</span><span class="n">tool</span>
<span class="p">{</span>
<span class="s2">"name"</span><span class="p">:</span> <span class="s2">"PyMIers"</span><span class="p">,</span>
<span class="s2">"year"</span><span class="p">:</span> <span class="mi">2020</span>
<span class="p">}</span>
</code></pre></div>
<p>Hoặc tìm các Palindromes trong file:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">python3</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="p">:</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">line</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span> <span class="ow">and</span> <span class="n">line</span> <span class="o">==</span> <span class="n">line</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="s1">' < /usr/share/dict/american-english | head -5</span>
<span class="n">ada</span>
<span class="n">ana</span>
<span class="n">anna</span>
<span class="n">ara</span>
<span class="n">ava</span>
</code></pre></div>
<p>Cách khác:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">python3</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'</span>
<span class="kn">import</span> <span class="nn">fileinput</span>
<span class="k">for</span> <span class="n">line</span> <span class="ow">in</span> <span class="n">fileinput</span><span class="o">.</span><span class="n">input</span><span class="p">():</span>
<span class="n">line</span> <span class="o">=</span> <span class="n">line</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">lower</span><span class="p">()</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">line</span><span class="p">)</span> <span class="o">></span> <span class="mi">1</span> <span class="ow">and</span> <span class="n">line</span> <span class="o">==</span> <span class="n">line</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">]:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="s1">' /usr/share/dict/american-english | head -3</span>
<span class="n">ada</span>
<span class="n">ana</span>
<span class="n">anna</span>
</code></pre></div>
<h3>Thử thách</h3>
<p>Viết lại ví dụ sau chỉ dùng các command line, không dùng Python:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">cat</span> <span class="o">/</span><span class="n">etc</span><span class="o">/</span><span class="n">passwd</span> <span class="o">|</span> <span class="n">python</span> <span class="o">-</span><span class="n">c</span> <span class="s1">'</span>
<span class="o">></span> <span class="kn">import</span> <span class="nn">sys</span>
<span class="o">></span> <span class="nb">print</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">stdin</span><span class="o">.</span><span class="n">readline</span><span class="p">()</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s2">":"</span><span class="p">)[</span><span class="o">-</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span>
<span class="o">></span> <span class="s1">'</span>
<span class="o">/</span><span class="n">BIN</span><span class="o">/</span><span class="n">BASH</span>
</code></pre></div>
<h3>Pro tips</h3>
<ul>
<li>trong code không dùng dấu <code>'</code></li>
<li><strong>không được gõ sai</strong> vì sửa lại hơi mệt, nếu không có khả năng này,
hãy viết code vào file.</li>
</ul>
<h2>Kết luận</h2>
<p>Python dài và chất, đừng ngại dùng khi gõ CLI.</p>Lib requests có gì hay mà dùng thay urllib2020-03-18T00:00:00+07:002020-03-18T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-03-18:/article/requests/<p>Giải mã thành công của thư viện được dùng nhiều nhất trong Python</p><p>Python là một ngôn ngữ già, có thể bạn chưa biết, <a href="https://www.familug.org/2016/02/python-python-tuoi-gi.html">Python tuổi
dê</a>
Python ra đời <a href="https://en.wikipedia.org/wiki/History_of_the_World_Wide_Web#1980%E2%80%931991:_Invention_and_implementation">từ thời mới có
HTTP</a>,
và nổi tiếng là <a href="https://xkcd.com/353/">hỗ trợ tận răng</a>, nên không
có gì lạ nếu Python có kèm sẵn thư viện standard để thực hiện HTTP request
với tên <code>urllib</code>.</p>
<p>Vậy nhưng khi lên mạng tìm kiếm hay hỏi quanh đây: <strong>dùng gì để gọi HTTP
trong Python?</strong>, câu trả lời phần lớn đều là cài: <code>pip install requests</code>.</p>
<p>Requests không phải có từ ngày Python xuất hiện, nhưng vào thời Python 2.6 2.7
(cỡ 2012-2013), requests đã rất phổ biến, ví dụ như câu trả lời <a href="https://stackoverflow.com/a/15869929/807703">trên
StackOverFlow năm 2013</a>.</p>
<p>Requests (có chữ s) xuất hiện với một API cực kỳ thân thiệt, với motto (khẩu
hiệu):
<strong>Python HTTP for Humans</strong> do <code>urllib</code> có sẵn trong Python2 quá rắc rối.
API của requests nổi tiếng đến mức <a href="https://github.com/levigross/grequests">gần như ngôn ngữ lập trình nào cũng
có một thư viện "nhái" requests của
Python</a>, nó quá đơn giản, tới mức
... trước đây không thư viện nào từng làm vậy.</p>
<p>(API của thư viện là các function mà thư viện đó public cho người dùng sử dụng,
ví dụ requests có: requests.get, requests.post.)</p>
<p><img alt="requests logo" src="https://raw.githubusercontent.com/psf/requests/master/ext/requests-logo.png"></p>
<p>Sau gần chục năm phát triển, <code>requests</code> giờ đã nằm dưới mái nhà <a href="https://github.com/psf/requests/">Python Software
Foundation</a>.</p>
<p>Với các tính năng được quảng cáo ở bản <a href="https://github.com/psf/requests/tree/v1.0.0">v1.0.0, cuối năm 2012</a></p>
<div class="highlight"><pre><span></span><code> <span class="n">International</span> <span class="n">Domains</span> <span class="ow">and</span> <span class="n">URLs</span>
<span class="n">Keep</span><span class="o">-</span><span class="n">Alive</span> <span class="o">&</span> <span class="n">Connection</span> <span class="n">Pooling</span>
<span class="n">Sessions</span> <span class="n">with</span> <span class="n">Cookie</span> <span class="n">Persistence</span>
<span class="n">Browser</span><span class="o">-</span><span class="n">style</span> <span class="n">SSL</span> <span class="n">Verification</span>
<span class="n">Basic</span><span class="o">/</span><span class="n">Digest</span> <span class="n">Authentication</span>
<span class="n">Elegant</span> <span class="n">Key</span><span class="o">/</span><span class="n">Value</span> <span class="n">Cookies</span>
<span class="n">Automatic</span> <span class="n">Decompression</span>
<span class="n">Unicode</span> <span class="n">Response</span> <span class="n">Bodies</span>
<span class="n">Multipart</span> <span class="n">File</span> <span class="n">Uploads</span>
<span class="n">Connection</span> <span class="n">Timeouts</span>
<span class="n">Thread</span><span class="o">-</span><span class="n">safety</span>
</code></pre></div>
<p>Có thể dễ đoán rằng những tính năng được quảng cáo trên chính là những gì
<code>urllib</code> Python thời đó chưa hỗ trợ (trong Python còn có cả thư viện tên
<code>urllib2</code>... để thêm phần phức tạp). Nhưng với Python 3.5 trở đi, rất nhiều
trong số trên đã được hỗ trợ trong <code>urllib</code>.</p>
<p>Còn đây là các tính năng được quảng cáo ở <a href="https://github.com/psf/requests/blob/v2.23.0/README.md#supported-features--bestpractices">phiên bản mới nhất</a>:</p>
<div class="highlight"><pre><span></span><code> <span class="n">Keep</span><span class="o">-</span><span class="n">Alive</span> <span class="o">&</span> <span class="n">Connection</span> <span class="n">Pooling</span>
<span class="n">International</span> <span class="n">Domains</span> <span class="ow">and</span> <span class="n">URLs</span>
<span class="n">Sessions</span> <span class="n">with</span> <span class="n">Cookie</span> <span class="n">Persistence</span>
<span class="n">Browser</span><span class="o">-</span><span class="n">style</span> <span class="n">SSL</span> <span class="n">Verification</span>
<span class="n">Automatic</span> <span class="n">Content</span> <span class="n">Decoding</span>
<span class="n">Basic</span><span class="o">/</span><span class="n">Digest</span> <span class="n">Authentication</span>
<span class="n">Elegant</span> <span class="n">Key</span><span class="o">/</span><span class="n">Value</span> <span class="n">Cookies</span>
<span class="n">Automatic</span> <span class="n">Decompression</span>
<span class="n">Unicode</span> <span class="n">Response</span> <span class="n">Bodies</span>
<span class="n">HTTP</span><span class="p">(</span><span class="n">S</span><span class="p">)</span> <span class="n">Proxy</span> <span class="n">Support</span>
<span class="n">Multipart</span> <span class="n">File</span> <span class="n">Uploads</span>
<span class="n">Streaming</span> <span class="n">Downloads</span>
<span class="n">Connection</span> <span class="n">Timeouts</span>
<span class="n">Chunked</span> <span class="n">Requests</span>
<span class="o">.</span><span class="n">netrc</span> <span class="n">Support</span>
</code></pre></div>
<p>Nếu bạn đọc các tính năng này mà không hiểu gì, thì nó chỉ chứng minh một điều
là HTTP là một giao thức rất lằng nhằng và phức tạp.
Đáng kể nhất có:</p>
<ul>
<li>Browser-style SSL Verification: liên quan tới SSL - tức là bảo mật. <code>urllib</code>
mãi tới <a href="https://docs.python.org/3/library/http.client.html#http.client.HTTPSConnection">Python 3.4.3 mới thực hiện kiểm tra SSL một cách mặc
định</a>:</li>
</ul>
<blockquote>
<p>Changed in version 3.4.3: This class now performs all the necessary
certificate and hostname checks by default. To revert to the previous,
unverified, behavior ssl._create_unverified_context() can be passed to
the context parameter.</p>
</blockquote>
<ul>
<li>Connection Pooling: bình thường khi viết code truy cập vào 1 website, ta
có thể nghĩ đơn giản là requests.get rồi lấy kết quả là xong chuyện, hết phiên.
Nếu muốn truy cập trang khác cùng website đó, ta lại requests.get để truy cập
mới. Phía dưới <code>requests.get</code> thực hiện tạo 1 <code>TCP connection</code>, sau đó mới gửi
<code>HTTP request</code> qua <code>connection</code> này. Việc tạo
connection là công việc khá tốn kém (CPU, thời gian), đặc biệt với HTTPS,
tạo SSL connection còn tốn hơn nhiều lần. Do vậy, để
tăng hiệu năng, requests sẽ tự giữ lại <code>connection</code> và dùng lại để truy cập
website nếu như các yêu cầu sau đó cùng website, khác page. Xem code tại
<a href="https://github.com/psf/requests/blob/v2.23.0/requests/adapters.py#L129">adapters.py</a>.</li>
</ul>
<p>Việc này ảnh hưởng tới hiệu năng, nhưng không ảnh hưởng gì nếu bạn chỉ gọi
1 request tới mỗi website.</p>
<h2>Đọc code requests</h2>
<p>Lib <code>requests</code> thuộc loại nhỏ, tổng cộng 5000 dòng gồm rất nhiều comment.
Nó tận dụng các thư viện ngoài khác thay vì tự làm
tất cả: <code>urllib3</code> để thực hiện connection pooling, thực hiện HTTP requests,
dùng <code>certifi</code> để cung cấp các SSL certificate mới nhất như các trình duyệt.</p>
<div class="highlight"><pre><span></span><code>$ wc -l *<span class="p">|</span> sort -nr
wc: __pycache__: Is a directory
<span class="m">5049</span> total
<span class="m">982</span> utils.py
<span class="m">954</span> models.py
<span class="m">767</span> sessions.py
<span class="m">549</span> cookies.py
<span class="m">533</span> adapters.py
<span class="m">305</span> auth.py
<span class="m">161</span> api.py
<span class="m">131</span> __init__.py
<span class="m">126</span> exceptions.py
<span class="m">123</span> status_codes.py
<span class="m">119</span> help.py
<span class="m">105</span> structures.py
<span class="m">72</span> compat.py
<span class="m">42</span> _internal_utils.py
<span class="m">34</span> hooks.py
<span class="m">18</span> certs.py
<span class="m">14</span> __version__.py
<span class="m">14</span> packages.py
</code></pre></div>
<p>Phần API đơn giản lừng danh nằm trong file <code>api.py</code>, trích bỏ comment:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">.</span> <span class="kn">import</span> <span class="n">sessions</span>
<span class="k">def</span> <span class="nf">request</span><span class="p">(</span><span class="n">method</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="c1"># By using the 'with' statement we are sure the session is closed, thus we</span>
<span class="c1"># avoid leaving sockets open which can trigger a ResourceWarning in some</span>
<span class="c1"># cases, and look like a memory leak in others.</span>
<span class="k">with</span> <span class="n">sessions</span><span class="o">.</span><span class="n">Session</span><span class="p">()</span> <span class="k">as</span> <span class="n">session</span><span class="p">:</span>
<span class="k">return</span> <span class="n">session</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="n">method</span><span class="o">=</span><span class="n">method</span><span class="p">,</span> <span class="n">url</span><span class="o">=</span><span class="n">url</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s1">'allow_redirects'</span><span class="p">,</span> <span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="s1">'get'</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">params</span><span class="o">=</span><span class="n">params</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">options</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s1">'allow_redirects'</span><span class="p">,</span> <span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="s1">'options'</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">head</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">kwargs</span><span class="o">.</span><span class="n">setdefault</span><span class="p">(</span><span class="s1">'allow_redirects'</span><span class="p">,</span> <span class="kc">False</span><span class="p">)</span>
<span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="s1">'head'</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">post</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="s1">'post'</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">data</span><span class="p">,</span> <span class="n">json</span><span class="o">=</span><span class="n">json</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">put</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="s1">'put'</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">data</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">patch</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="kc">None</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="s1">'patch'</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">data</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">delete</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">return</span> <span class="n">request</span><span class="p">(</span><span class="s1">'delete'</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</code></pre></div>
<h3>urllib3</h3>
<p><a href="https://urllib3.readthedocs.io/en/latest/"><code>urllib3</code></a> là dependency quan trọng
của <code>requests</code>, nó đảm nhận những công việc nặng nề:</p>
<blockquote>
<p>urllib3 brings many critical features that are missing from the Python
standard libraries:</p>
</blockquote>
<div class="highlight"><pre><span></span><code><span class="n">Thread</span> <span class="n">safety</span><span class="o">.</span>
<span class="n">Connection</span> <span class="n">pooling</span><span class="o">.</span>
<span class="n">Client</span><span class="o">-</span><span class="n">side</span> <span class="n">SSL</span><span class="o">/</span><span class="n">TLS</span> <span class="n">verification</span><span class="o">.</span>
<span class="n">File</span> <span class="n">uploads</span> <span class="n">with</span> <span class="n">multipart</span> <span class="n">encoding</span><span class="o">.</span>
<span class="n">Helpers</span> <span class="k">for</span> <span class="n">retrying</span> <span class="n">requests</span> <span class="ow">and</span> <span class="n">dealing</span> <span class="n">with</span> <span class="n">HTTP</span> <span class="n">redirects</span><span class="o">.</span>
<span class="n">Support</span> <span class="k">for</span> <span class="n">gzip</span> <span class="ow">and</span> <span class="n">deflate</span> <span class="n">encoding</span><span class="o">.</span>
<span class="n">Proxy</span> <span class="n">support</span> <span class="k">for</span> <span class="n">HTTP</span> <span class="ow">and</span> <span class="n">SOCKS</span><span class="o">.</span>
</code></pre></div>
<p>Code mẫu:</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">urllib3</span>
<span class="o">>>></span> <span class="n">http</span> <span class="o">=</span> <span class="n">urllib3</span><span class="o">.</span><span class="n">PoolManager</span><span class="p">()</span>
<span class="o">>>></span> <span class="n">r</span> <span class="o">=</span> <span class="n">http</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="s1">'GET'</span><span class="p">,</span> <span class="s1">'http://httpbin.org/robots.txt'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">r</span><span class="o">.</span><span class="n">status</span>
<span class="mi">200</span>
<span class="o">>>></span> <span class="n">r</span><span class="o">.</span><span class="n">data</span>
<span class="s1">'User-agent: *</span><span class="se">\n</span><span class="s1">Disallow: /deny</span><span class="se">\n</span><span class="s1">'</span>
</code></pre></div>
<h2>urllib</h2>
<p>urllib và urllib2 thời Python2.7 là những em gái dính lời nguyền mà ai cũng
muốn tha thứ. Nhưng ở Python 3.6+, việc dùng urllib không còn quá phức tạp,
hãy coi nó như 1 file, nhớ đóng file, hoặc dùng <code>with</code>.</p>
<h3>urllib đã kiểm tra SSL certificate</h3>
<div class="highlight"><pre><span></span><code><span class="n">Python</span> <span class="mf">3.6.9</span> <span class="p">(</span><span class="n">default</span><span class="p">,</span> <span class="n">Nov</span> <span class="mi">7</span> <span class="mi">2019</span><span class="p">,</span> <span class="mi">10</span><span class="p">:</span><span class="mi">44</span><span class="p">:</span><span class="mi">02</span><span class="p">)</span>
<span class="p">[</span><span class="n">GCC</span> <span class="mf">8.3.0</span><span class="p">]</span> <span class="n">on</span> <span class="n">linux</span>
<span class="n">Type</span> <span class="s2">"help"</span><span class="p">,</span> <span class="s2">"copyright"</span><span class="p">,</span> <span class="s2">"credits"</span> <span class="ow">or</span> <span class="s2">"license"</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">urllib.request</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">urllib.request</span> <span class="kn">import</span> <span class="n">urlopen</span>
<span class="o">>>></span> <span class="k">with</span> <span class="n">urlopen</span><span class="p">(</span><span class="s2">"https://dantri.com"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="o">...</span> <span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="o">...</span> <span class="nb">print</span><span class="p">(</span><span class="n">content</span><span class="p">[:</span><span class="mi">100</span><span class="p">])</span>
<span class="o">...</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
<span class="n">File</span> <span class="s2">"/usr/lib/python3.6/urllib/request.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1318</span><span class="p">,</span> <span class="ow">in</span> <span class="n">do_open</span>
<span class="o">...</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_sslobj</span><span class="o">.</span><span class="n">do_handshake</span><span class="p">()</span>
<span class="n">ssl</span><span class="o">.</span><span class="n">SSLError</span><span class="p">:</span> <span class="p">[</span><span class="n">SSL</span><span class="p">:</span> <span class="n">CERTIFICATE_VERIFY_FAILED</span><span class="p">]</span> <span class="n">certificate</span> <span class="n">verify</span> <span class="n">failed</span> <span class="p">(</span><span class="n">_ssl</span><span class="o">.</span><span class="n">c</span><span class="p">:</span><span class="mi">852</span><span class="p">)</span>
<span class="o">...</span>
<span class="k">raise</span> <span class="n">URLError</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="n">urllib</span><span class="o">.</span><span class="n">error</span><span class="o">.</span><span class="n">URLError</span><span class="p">:</span> <span class="o"><</span><span class="n">urlopen</span> <span class="n">error</span> <span class="p">[</span><span class="n">SSL</span><span class="p">:</span> <span class="n">CERTIFICATE_VERIFY_FAILED</span><span class="p">]</span> <span class="n">certificate</span> <span class="n">verify</span> <span class="n">failed</span> <span class="p">(</span><span class="n">_ssl</span><span class="o">.</span><span class="n">c</span><span class="p">:</span><span class="mi">852</span><span class="p">)</span><span class="o">></span>
</code></pre></div>
<p>Kết quả tương tự khi dùng <code>requests</code> do URL <code>https://dantri.com</code> SSL certificate
đẫ hết hạn <code>Expire: January 19, 2020</code></p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">requests</span>
<span class="o">>>></span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"https://dantri.com"</span><span class="p">)</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
<span class="n">File</span> <span class="s2">"/home/hvn/py3/lib/python3.6/site-packages/urllib3/connectionpool.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">672</span><span class="p">,</span> <span class="ow">in</span> <span class="n">urlopen</span>
<span class="o">...</span>
<span class="n">File</span> <span class="s2">"/usr/lib/python3.6/ssl.py"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">689</span><span class="p">,</span> <span class="ow">in</span> <span class="n">do_handshake</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_sslobj</span><span class="o">.</span><span class="n">do_handshake</span><span class="p">()</span>
<span class="n">ssl</span><span class="o">.</span><span class="n">SSLError</span><span class="p">:</span> <span class="p">[</span><span class="n">SSL</span><span class="p">:</span> <span class="n">CERTIFICATE_VERIFY_FAILED</span><span class="p">]</span> <span class="n">certificate</span> <span class="n">verify</span> <span class="n">failed</span> <span class="p">(</span><span class="n">_ssl</span><span class="o">.</span><span class="n">c</span><span class="p">:</span><span class="mi">852</span><span class="p">)</span>
</code></pre></div>
<h3>urllib redirect ngon lành</h3>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s2">"https://gmail.com"</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">r</span><span class="o">.</span><span class="n">url</span>
<span class="s1">'https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue=https://mail.google.com/mail/&ss=1&scc=1&ltmpl=default&ltmplcache=2&emr=1&osid=1'</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">urllib.request</span> <span class="kn">import</span> <span class="n">urlopen</span>
<span class="o">>>></span> <span class="n">r</span> <span class="o">=</span> <span class="n">urlopen</span><span class="p">(</span><span class="s2">"https://gmail.com"</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">r</span><span class="o">.</span><span class="n">url</span>
<span class="s1">'https://accounts.google.com/ServiceLogin?service=mail&passive=true&rm=false&continue=https://mail.google.com/mail/&ss=1&scc=1&ltmpl=default&ltmplcache=2&emr=1&osid=1'</span>
</code></pre></div>
<h3>urllib với JSON API</h3>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">json</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">urllib.request</span> <span class="kn">import</span> <span class="n">urlopen</span>
<span class="o">>>></span> <span class="k">with</span> <span class="n">urlopen</span><span class="p">(</span><span class="s2">"https://httpbin.org/ip"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="o">...</span> <span class="n">content</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">f</span><span class="p">)</span>
<span class="o">...</span> <span class="nb">print</span><span class="p">(</span><span class="n">content</span><span class="p">)</span>
<span class="o">...</span>
<span class="p">{</span><span class="s1">'origin'</span><span class="p">:</span> <span class="s1">'171.247.169.69'</span><span class="p">}</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">json</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">urllib.request</span> <span class="kn">import</span> <span class="n">Request</span><span class="p">,</span> <span class="n">urlopen</span>
<span class="o">>>></span> <span class="k">with</span> <span class="n">urlopen</span><span class="p">(</span>
<span class="o">...</span> <span class="n">Request</span><span class="p">(</span><span class="s2">"https://httpbin.org/post"</span><span class="p">,</span> <span class="n">method</span><span class="o">=</span><span class="s2">"POST"</span><span class="p">,</span>
<span class="o">...</span> <span class="n">data</span><span class="o">=</span><span class="n">json</span><span class="o">.</span><span class="n">dumps</span><span class="p">({</span><span class="s2">"name"</span><span class="p">:</span> <span class="s2">"Pymi"</span><span class="p">,</span> <span class="s2">"since"</span><span class="p">:</span> <span class="mi">2015</span><span class="p">})</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">),</span>
<span class="o">...</span> <span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s1">'Content-Type'</span><span class="p">:</span> <span class="s2">"application/json"</span><span class="p">})</span>
<span class="o">...</span> <span class="p">)</span> <span class="k">as</span> <span class="n">resp</span><span class="p">:</span>
<span class="o">...</span> <span class="nb">print</span><span class="p">(</span><span class="n">json</span><span class="o">.</span><span class="n">load</span><span class="p">(</span><span class="n">resp</span><span class="p">)[</span><span class="s1">'json'</span><span class="p">])</span>
<span class="o">...</span>
<span class="p">{</span><span class="s1">'name'</span><span class="p">:</span> <span class="s1">'Pymi'</span><span class="p">,</span> <span class="s1">'since'</span><span class="p">:</span> <span class="mi">2015</span><span class="p">}</span>
</code></pre></div>
<h3>Tra cứu thông tin COVID-19</h3>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="k">with</span> <span class="n">urlopen</span><span class="p">(</span><span class="s2">"https://corona-stats.online/IT"</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="o">...</span> <span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s2">"utf-8"</span><span class="p">))</span>
<span class="o">...</span>
<span class="err">╔═══════╤═══════╤═══════════╤═══════════╤════════╤════════╤═════════════╤═════════════╤═════════╤══════════╗</span>
<span class="err">║</span> <span class="err">│</span> <span class="n">State</span> <span class="err">│</span> <span class="n">Confirmed</span> <span class="err">│</span> <span class="n">Recovered</span> <span class="err">│</span> <span class="n">Deaths</span> <span class="err">│</span> <span class="n">Active</span> <span class="err">│</span> <span class="n">Mortality</span> <span class="o">%</span> <span class="err">│</span> <span class="n">Recovered</span> <span class="o">%</span> <span class="err">│</span> <span class="mi">1</span> <span class="n">Day</span> <span class="err">▲</span> <span class="err">│</span> <span class="mi">1</span> <span class="n">Week</span> <span class="err">▲</span> <span class="err">║</span>
<span class="err">╟───────┼───────┼───────────┼───────────┼────────┼────────┼─────────────┼─────────────┼─────────┼──────────╢</span>
<span class="err">║</span> <span class="n">Italy</span> <span class="err">│</span> <span class="n">Total</span> <span class="err">│</span> <span class="mi">31</span><span class="p">,</span><span class="mi">506</span> <span class="err">│</span> <span class="mi">2</span><span class="p">,</span><span class="mi">941</span> <span class="err">│</span> <span class="mi">2</span><span class="p">,</span><span class="mi">503</span> <span class="err">│</span> <span class="mi">26</span><span class="p">,</span><span class="mi">062</span> <span class="err">│</span> <span class="mf">7.94</span> <span class="err">│</span> <span class="mf">9.33</span> <span class="err">│</span> <span class="mi">3</span><span class="p">,</span><span class="mi">526</span> <span class="err">▲</span> <span class="err">│</span> <span class="mi">21</span><span class="p">,</span><span class="mi">357</span> <span class="err">▲</span> <span class="err">║</span>
<span class="err">╚═══════╧═══════╧═══════════╧═══════════╧════════╧════════╧═════════════╧═════════════╧═════════╧══════════╝</span>
<span class="n">Stay</span> <span class="n">safe</span><span class="o">.</span> <span class="n">Stay</span> <span class="n">inside</span><span class="o">.</span>
<span class="n">Code</span><span class="p">:</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">github</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">sagarkarira</span><span class="o">/</span><span class="n">coronavirus</span><span class="o">-</span><span class="n">tracker</span><span class="o">-</span><span class="n">cli</span>
<span class="n">Twitter</span><span class="p">:</span> <span class="n">https</span><span class="p">:</span><span class="o">//</span><span class="n">twitter</span><span class="o">.</span><span class="n">com</span><span class="o">/</span><span class="n">ekrysis</span>
<span class="n">Last</span> <span class="n">Updated</span> <span class="n">on</span><span class="p">:</span> <span class="mi">18</span><span class="o">-</span><span class="n">Mar</span><span class="o">-</span><span class="mi">2020</span> <span class="mi">16</span><span class="p">:</span><span class="mi">03</span> <span class="n">UTC</span>
</code></pre></div>
<h2>Hành động của chúng ta</h2>
<p>Có thể dùng <code>urllib</code> khi script/chương trình chỉ truy cập mỗi website
một lần, dùng trong các script ngắn, hay khi không tiện cài <code>requests</code>.
Nhớ sử dụng <a href="https://requests.readthedocs.io/en/v2.9.1/user/advanced/#session-objects"><code>requests</code> Session</a>
khi truy cập 1 website nhiều lần để tăng hiệu năng.</p>
<h2>Kết luận</h2>
<p>Requests thành công vì sự đơn giản không thể hơn của nó, chứ không phải vì
kỹ thuật cao siêu phức tạp.</p>
<blockquote>
<p>A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.
The New Hacker's Dictionary - Eric S. Raymond,</p>
</blockquote>
<p>Nhớ mặc định là dùng <code>requests</code>, nhưng không bị sốc khi thấy người ta dùng
<code>urllib</code>.</p>Django dễ hơn Flask2020-03-16T00:00:00+07:002020-03-16T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-03-16:/article/djangovsflask/<p>Flask là một micro-framework, thường được xem như đơn giản hơn Django, nhưng nói vậy mà không phải vậy.</p><p>Django và Flask là hai web-framework phổ biến nhất - thành công nhất của Python.
Ngày này, khi nhìn các bảng tuyển dụng, sẽ thường thấy yêu cầu Django để làm web
nhưng Flask dần trở thành công cụ quen thuộc của giới làm data.
Nếu ai có hỏi "nên học cái nào" thì câu trả lời chuẩn nhất chỉ có một: nên học
cả hai.</p>
<h2>Web framework là gì</h2>
<p>Một framework là một bộ thư viện, thường gồm nhiều tính năng phục vụ đưa ra
sản phẩm cuối cùng chạm tới người dùng. Ví dụ <code>requests</code> là một library,
chỉ cung cấp tính năng HTTP client, hay <code>bs4</code> chỉ cung cấp tính năng bóc tách/
truy cập dữ liệu HTML, hay <code>sqlalchemy</code> là library giúp truy cập và tương tác
với database, thì Flask/Django cung cấp một bộ khung kèm các "thư viện" để
làm ra một sản phẩm tới tay người dùng (trang web).
Frame trong tiếng Anh có nghĩa là "khung", framework cung cấp cái khung, lập
trình viên điền những thứ mình muốn vào đó, framework sẽ tự lo chạy. Vì vậy
khi dùng framework, sẽ có cảm giác như ta không biết nó hoạt động thế nào,
chỉ biết là điền đúng những thứ yêu cầu thì nó sẽ hoạt động.</p>
<p>Django là framework đầy đủ (full-fledge), có đủ mọi thứ, nên sẽ càng có thêm
cảm giác ta chỉ điền chút xíu là nó tự chạy, mọi thứ đều có khuôn mẫu sẫn rồi.
Flask là micro-framework, chỉ có phần khung cơ bản, các phần còn lại cho phép
người dùng tùy chọn mà đắp vào, vậy nên có phần dễ hiểu hơn, ít phép màu hơn.</p>
<p>Cũng vì sự khác biệt này, dẫn tới tài liệu trang chủ của 2 framework viết
rất khác nhau. Flask vào đời bằng một website dùng 7 dòng code, khiến người dùng
rất dễ học, dễ bắt đầu với sự đơn giản này. Còn Django lại dùng ví dụ phức tạp,
mặc định người dùng sẽ cần làm thứ "phức tạp" như vậy nên học thế cho quen.</p>
<p>Thế nhưng nếu không dựa vào 2 cái tutorial đấy để đánh giá, liệu Django có
phức tạp hơn Flask, hay thậm chí còn đơn giản hơn? Bài nầy sẽ dựa trên 2 yếu
tố để so sánh: 1. số khái niệm bạn cần biết 2. số dòng code bạn cần viết.</p>
<p>Bắt đầu.</p>
<h3>Cài đặt</h3>
<h4>Cài đặt Flask</h4>
<div class="highlight"><pre><span></span><code>pip install flask
</code></pre></div>
<h4>Cài đặt Django</h4>
<div class="highlight"><pre><span></span><code>pip install django
</code></pre></div>
<p>Kết quả: hòa, dễ như nhau: <code>Flask 1 - Django 1</code></p>
<h3>Viết một website hello world và hello mình</h3>
<h4>Flask</h4>
<p>Tạo 1 file tên bất kỳ nhưng tốt nhất đặt là <code>hello.py</code> (tuyệt đối không đặt
là <code>flask.py</code> do trùng tên flask)</p>
<div class="highlight"><pre><span></span><code><span class="mi">1</span> <span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span>
<span class="mi">2</span> <span class="n">app</span> <span class="o">=</span> <span class="n">Flask</span><span class="p">(</span><span class="s2">"my website"</span><span class="p">)</span>
<span class="mi">3</span>
<span class="mi">4</span> <span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/"</span><span class="p">)</span>
<span class="mi">5</span> <span class="k">def</span> <span class="nf">hello_world</span><span class="p">():</span>
<span class="mi">6</span> <span class="k">return</span> <span class="s2">"Hello world"</span>
<span class="mi">7</span>
<span class="mi">8</span> <span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/hello/<string:name>"</span><span class="p">)</span>
<span class="mi">9</span> <span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">name</span><span class="p">):</span>
<span class="mi">10</span> <span class="k">return</span> <span class="s2">"Hello </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</code></pre></div>
<p>Chạy, <a href="https://flask.palletsprojects.com/en/1.1.x/quickstart/#a-minimal-application">theo hướng dẫn trên trang chủ</a>
(nếu dùng Windows vào xem lệnh tương ứng):</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">export</span> <span class="nv">FLASK_APP</span><span class="o">=</span>hello.py
$ flask run
* Running on http://127.0.0.1:5000/
</code></pre></div>
<p>Điểm ấn tượng nhất ở đây là chỉ có 10 dòng, trong đúng 1 file, mà chạy thành
trang web.</p>
<p>Tổng số thao tác phải làm: 3 - tạo 1 file, điền nội dung, gõ lệnh để chạy.</p>
<h4>Django</h4>
<p>Django không làm web trong 1 file, nó dùng các câu lệnh có sẵn để sinh ra các
file, tức ta chỉ phải gõ lệnh, chứ không phải gõ nhiều code.
Gõ 3 lệnh sau là chạy luôn trang web:</p>
<div class="highlight"><pre><span></span><code>$ django-admin startproject project1
$ <span class="nb">cd</span> project1/
$ ./manage.py startapp hello
$ ./manage.py runserver
Starting development server at http://127.0.0.1:8000/
</code></pre></div>
<p>Ngay lập tức đã có trang web mẫu của Django - thậm chí chưa gõ tí code nào.
Để có kết quả giống Flask ở trên, cần sửa 2 file:</p>
<p>File <code>hello/views.py</code>, viết các function tương tự flask:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="k">def</span> <span class="nf">hello_world</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="s2">"Hello world"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">hello</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">name</span><span class="p">):</span>
<span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="s2">"Hello </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">name</span><span class="p">))</span>
</code></pre></div>
<p>File <code>project1/urls.py</code> thêm 3 dòng:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">hello</span> <span class="kn">import</span> <span class="n">views</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span> <span class="c1"># có sẵn</span>
<span class="n">path</span><span class="p">(</span><span class="s1">'admin/'</span><span class="p">,</span> <span class="n">admin</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">urls</span><span class="p">),</span> <span class="c1"># có sẵn</span>
<span class="n">path</span><span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">hello_world</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'hello_world'</span><span class="p">),</span>
<span class="n">path</span><span class="p">(</span><span class="s1">'hello/<str:name>'</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">hello</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'hello'</span><span class="p">),</span>
<span class="p">]</span> <span class="c1"># có sẵn</span>
</code></pre></div>
<p>Giờ chạy <code>./manage runserver</code> sẽ có kết quả y hệt Flask.</p>
<h4>So sánh thao tác</h4>
<p>Tổng số dòng code phải thêm vào Django là 8 - cũng chính bằng số dòng Flask
phải viết - không tính dòng trống. Django phải chạy nhiều hơn 2 lệnh - nhưng
chỉ cần phải làm khi bắt đầu dự án. Flask viết code trong 1 file, còn Django
phải sửa 2 file. ở đây có thể xem như hòa nhau không bên nào hơn/kém quá cả.</p>
<h4>So sánh khái niệm</h4>
<p>Phần khác nhau chủ đạo giữa Django và Flask là cách ghi URL.
Người dùng Flask tạo một Flask object, đặt tên là app, sau đó sử dụng
<code>decorator</code></p>
<div class="highlight"><pre><span></span><code><span class="nv">@app</span><span class="p">.</span><span class="n">route</span><span class="p">(</span><span class="ss">"/hello/<string:name>"</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<p>để gắn đường dẫn <code>/hello/name</code> cho function ngay dưới nó (function hello).
Decorator không phải một khái niệm đơn giản ở đây, mặc dù người dùng có thể
cứ nhắm mắt rồi gõ <code>@</code> theo và hiểu nôm na là gắn đường dẫn cũng được.</p>
<p>Django chọn giải pháp ghép đường dẫn với function thông qua file <code>urls.py</code>,
import module <code>views</code> từ thư mục <code>hello/</code> (tạo lúc chạy "startapp hello"),
sau đó gán đường dẫn bằng cách gọi function <code>path</code>:</p>
<div class="highlight"><pre><span></span><code> <span class="n">path</span><span class="p">(</span><span class="s1">'hello/<str:name>'</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">hello</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'hello'</span><span class="p">),</span>
</code></pre></div>
<p>Dài dòng hơn Flask một chút là phải ghi <code>name="hello"</code>, không có <code>/</code> ở đầu URL,
và kiểu <code>str</code> thì ghi là <code>str</code> chứ không ghi <code>string</code> như Flask.</p>
<p>Khi mới bắt đầu, khó có thể thấy lý do Django viết riêng file <code>urls.py</code> để làm
gì, khi mà flask @ lên đầu rất gọn ghẽ. Thì tới lúc có 20 URL bạn sẽ hiểu tại
sao. Làm thế nào để liệt kê tất cả URL trong app của mình? Các URL của Flask
rải rác khắc nơi, mỗi cái nằm trên đầu 1 function, nếu như mỗi function dài 50
dòng, thì việc tìm sẽ khá vất vả - hoặc phải dùng chức năng tìm kiếm, hoặc phải
có IDE để hỗ trợ. Còn với Django tất cả đều nằm gọn trong <code>urls.py</code>.</p>
<p>Không hiểu tại sao Flask lại chọn <code>string</code> để ám chỉ kiểu string trong Python,
vốn viết là <code>str</code>, Django lại ghi thêm điểm ở đây.</p>
<p>Có thể bạn đã biết, rằng không nhất thiết phải dùng <code>@</code> trong Flask,
bởi decorator này chỉ làm đơn giản có 1 việc: giống Django. Thay vì viết hai
dòng <code>@app.route</code> trên đầu function, viết hai dòng này SAU khi định nghĩa
các function:</p>
<div class="highlight"><pre><span></span><code>app.add_url_rule("/", "hello_world", hello_world)
app.add_url_rule("/hello/<string:name>", "hello", hello)
</code></pre></div>
<p>Đây chính làm điều mà <code>@app.route</code> làm, như mô tả trong tài liệu của nó:</p>
<blockquote>
<p>A decorator that is used to register a view function for a
given URL rule. This does the same thing as :meth:<code>add_url_rule</code>
but is intended for decorator usage::</p>
</blockquote>
<p>Chốt: Django gõ nhiều hơn 2 lệnh, sửa thêm 1 file, nhưng sử dụng ít khái niệm
cao cấp hơn Flask. Nếu dễ tính, thì cho là hòa, còn không Django dành chiến
thắng ở đây.</p>
<p>Tỉ số: <code>2 - 2</code></p>
<h3>Thêm trang dùng template</h3>
<h4>Flask</h4>
<p>Tạo 1 thư mục tên <code>templates</code> ngay cạnh file <code>hello.py</code>, rồi tạo file
<code>index.html</code> chứa code HTML và Jinja2 template.</p>
<div class="highlight"><pre><span></span><code><span class="cp">{%</span> <span class="k">for</span> <span class="nv">fw</span> <span class="k">in</span> <span class="nv">frameworks</span> <span class="cp">%}</span>
<span class="cp">{%</span> <span class="k">if</span> <span class="nv">fw</span><span class="o">|</span><span class="nf">length</span> <span class="o">></span> <span class="m">5</span> <span class="cp">%}</span>
<span class="nt"><b></span><span class="cp">{{</span> <span class="nv">fw</span> <span class="cp">}}</span><span class="nt"></b></span>
<span class="cp">{%</span> <span class="k">else</span> <span class="cp">%}</span>
<span class="cp">{{</span> <span class="nv">fw</span> <span class="cp">}}</span>
<span class="cp">{%</span> <span class="k">endif</span> <span class="cp">%}</span>
<span class="cp">{%</span> <span class="k">endfor</span> <span class="cp">%}</span>
</code></pre></div>
<p>Code sửa thành</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">flask</span> <span class="kn">import</span> <span class="n">Flask</span><span class="p">,</span> <span class="n">render_template</span>
<span class="nd">@app</span><span class="o">.</span><span class="n">route</span><span class="p">(</span><span class="s2">"/web"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">frameworks</span><span class="p">():</span>
<span class="k">return</span> <span class="n">render_template</span><span class="p">(</span><span class="s2">"index.html"</span><span class="p">,</span> <span class="n">frameworks</span><span class="o">=</span><span class="p">[</span><span class="s2">"Flask"</span><span class="p">,</span> <span class="s2">"Django"</span><span class="p">])</span>
</code></pre></div>
<h4>Django</h4>
<p>Tạo thư mục tên <code>templates/hello</code> trong thư mục
<code>hello</code>, sau đó tạo <code>index.html</code> và nội dung copy y hệt phần template của Flask.</p>
<p>Thêm function sau vào <code>hello/views.py</code></p>
<div class="highlight"><pre><span></span><code><span class="c1"># chú ý render đã được import sẵn ở file view.py ngay khi tạo app</span>
<span class="k">def</span> <span class="nf">frameworks</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="k">return</span> <span class="n">render</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="s2">"hello/index.html"</span><span class="p">,</span> <span class="p">{</span><span class="s2">"frameworks"</span><span class="p">:</span> <span class="p">[</span><span class="s2">"Flask"</span><span class="p">,</span> <span class="s2">"Django"</span><span class="p">]})</span>
</code></pre></div>
<p>Và thêm 1 dòng vào <code>project1/urls.py</code>:</p>
<div class="highlight"><pre><span></span><code> path('web', views.frameworks, name='frameworks'),
</code></pre></div>
<p>Ngoài ra phải thêm <code>'hello'</code> vào file <code>project1/settings.py</code>
Trong list <code>INSTALLED_APPS</code></p>
<div class="highlight"><pre><span></span><code> <span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">[</span>
<span class="o">...</span><span class="p">,</span>
<span class="s1">'hello'</span><span class="p">,</span>
<span class="p">]</span>
</code></pre></div>
<p>Phần này lại hòa, template mặc định của Flask là <code>jinja2</code> tương đương với
Django template, kể cả cú pháp cũng rất giống nhau. Django phải thêm 1
dòng vào file <code>settings.py</code> nhưng việc này cũng chỉ phải làm một lần duy nhất
(lẽ ra nên làm luôn sau khi createapp từ đầu).</p>
<h3>Django app</h3>
<p>Khi chạy lệnh <code>startproject</code> ban đầu, nó sinh ra các file cần thiết cho một
dự án Django (<code>urls.py</code>, <code>settings.py</code>, <code>manager.py</code> ..., và nó chỉ phải chạy
duy nhất 1 lần trong 1 dự án. Với Flask, bạn tạo thư mục bằng tay.</p>
<p>Django đưa ra khái niệm có nhiều <em>app</em> trong một dự án. Flask cũng có khái
niệm tương tự, gọi là <a href="https://flask.palletsprojects.com/en/1.1.x/blueprints/">Blueprint</a>.
Nó không cần thiết cho ví dụ 6-10 dòng, nhưng khi làm một website nhiều tính năng
thì blueprint là cách tổ chức chính thức của Flask, ngay cả trong bài tutorial
của Flask cũng dùng luôn khái niệm này. Như vậy chỉ khác nhau việc học trước
hay học sau, chứ không phải Flask thì không dùng tới.</p>
<p>Flask không ép (không hướng dẫn) người dùng cách tổ chức code, khi dự án lớn
dần lên, có 3-5000 dòng code, ắt phải sinh ra file mới, phải sắp xếp, tổ chức
sao cho hợp lý - mà thế nào là hợp lý thì không phải ai cũng tìm ngay ra cách.
Dự án Flask khi rơi vào tay lập trình viên chưa có nhiều kinh nghiệm làm web
sẽ dẫn tới code mỗi thứ một nơi, đi tìm cũng thấy mệt. Flask không hướng dẫn
người dùng ngay từ đầu, nếu gặp phải người chưa có kinh nghiệm tổ chức, sẽ
trở thành vô tổ chức.</p>
<p>Django bắt đầu bằng việc đặt mỗi thứ vào một nơi quy định sẵn, ban đầu có vẻ
hơi phiền phức, nhưng sự gọn gàng ngăn nắp sẽ trả lợi ích rõ ràng về sau.
Bạn có thể xem 10 dự án Django, cấu trúc đều như nhau, như mẫu của Django,
thì 10 dự án Flask mỗi cái một kiểu.</p>
<p>Tỉ số: Flask 2 - Django 3</p>
<h3>Thêm Database, thêm tính năng đăng nhập, thêm trang admin</h3>
<p>Đến đây, việc của người dùng Django là mở tài liệu ra, đọc phần model, đọc phần
admin page rồi làm theo hướng dẫn, đầy đủ hàng trăm tính năng có sẵn, thì
người dùng Flask lại phải lo đi tìm:</p>
<ul>
<li>dùng database nào? nếu dùng MySQL thì dùng driver nào?</li>
<li>có dùng ORM không? nếu có thì phải học SQLAlchemy, tương đương với học Django ORM</li>
<li>dùng SQLAlchemy rồi có phải dùng flask-sqlalchemy không (có).</li>
<li>khi đổi schema table (thêm/bớt/sửa cột trong 1 SQL table) thì phải làm thế nào?
MigrateDB thì dùng cái gì? (Flask-Migrate / alembic)</li>
<li>Viết trang admin (ví dụ website quản lý sinh viên thì đây là trang để thêm/bớt/sửa/xóa sinh viên)
thì viết ra sao (có thể dùng flask-admin)</li>
<li>Đăng nhập thì làm thế nào? (flask-httpauth)</li>
<li>Nhập/validate form thì dùng cái chi? (WTForm)</li>
<li>... tiếp tục ...</li>
</ul>
<p><a href="https://www.youtube.com/watch?v=AdfSLq7XNoI" title="Tìm"><img alt="Tìm MIN" src="http://img.youtube.com/vi/AdfSLq7XNoI/0.jpg"></a></p>
<p>Tỉ số: Flask 2 - Django 4</p>
<h3>Kết quả</h3>
<p>Hòa.</p>
<h2>Hành động của chúng ta</h2>
<p><code>pip install django</code> dùng ngay và luôn thôi.</p>
<h2>Kết luận</h2>
<p>Bài này không nói Django hoàn hảo, dùng cho mọi trường hợp. Ví dụ nếu
website của bạn không dùng database, không cần trang admin (như web của data
science) thì Flask nhanh, nhẹ hơn Django nhiều phần. Hay khi bạn biết mình đang
làm gì, biết đủ những thư viện để dùng ghép lại xịn hơn cả những gì Django
có sẵn, thì Flask là lựa chọn tuyệt hảo. Flask không phải đồ chơi, <a href="https://eng.uber.com/building-tincup-microservice-implementation/">phần
core của Uber đã từng được viết bằng
Flask</a>,
<a href="https://airflow.apache.org/">Airflow</a> - công cụ không thể thiếu trong các hệ
thống data cũng chính là một sản phẩm dùng Flask. Vậy nên, hãy học cả 2, nhé.
Và nhớ là, Django không hề khó như bạn tưởng.</p>black - quên đi nỗi lo PEP82020-03-15T00:00:00+07:002020-03-15T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-03-15:/article/black/<p>Viết code Python thì phải chuẩn PEP8, nếu nó tự chuẩn thì sao? yeah!!!</p><h2>Code Python phải chuẩn PEP8</h2>
<p>Ngay khi bạn mới làm quen với Python được vài tuần, mới biết tí for loop hay
function, PEP8 từ đâu đó sẽ hiện ra.
PEP8 là hướng dẫn viết code chuẩn Python, chứ không phải chuẩn C, Java, hay
không có chuẩn nào cả.
Nếu tự hoc code một mình, có khi giờ này bạn đã làm ra vài chục script, chạy
ầm ầm mà vẫn chưa 1 lần nghe PEP8. Tiêu chuẩn code là thứ chỉ hiện ra rõ ràng,
khi ta làm việc với người khác, chỉ sau vài function, sự mâu thuẫn về style
sẽ hiện ra ngay, và khi ông Chí Phèo không đồng ý với style của Bá Kiến, thì
cả 2 phải lên phường và thống nhất dùng chuẩn chung mà làng Vũ Đại đặt ra - hay
ở đây ta gọi là PEP8.</p>
<p>PEP8 có thể xem chi tiết tại <a href="https://www.python.org/dev/peps/pep-0008/">PEP0008</a>
hay <a href="https://pep8.org/">https://pep8.org/</a>,
tiêu chuẩn PEP8 được chấp nhận trên toàn trái đất, thậm chí <a href="https://medium.com/pymi/cia-d%C3%B9ng-python-r%E1%BA%A5t-nhi%E1%BB%81u-32144ecddd50">CIA</a>,
Google cũng
chỉ sửa đi chút xíu, bởi nó là chuẩn hay, chuẩn tốt.</p>
<h2>Chuyện đau đầu về xì tai (style)</h2>
<p>Style vốn là một thứ dễ gây ra tranh cãi. Tôi thích kiểu Việt Nam dịu dàng,
anh thích kiểu Pháp say đắm, ông kia thích kiểu Mỹ mạnh mẽ và hùng hục. Vậy
ai là người sai?
Cuộc tranh cãi về style viết code đã kéo dài suốt từ ngày lập trình xuất hiện,
tới giờ vẫn chưa kết thúc. Bởi đã là style, thì khó nói chuyện đúng sai.</p>
<p>Thế rồi mọi cuộc chơi vui, cũng phải đến hồi kết. Một ngôn ngữ lập trình đơn
giản xuất hiện với tên hai chữ <code>Go</code> (sau để tránh nhầm lẫn thì gọi là Golang),
và để tăng thêm sự đơn giản, nó đi kèm
sẵn một chương trình với tên <code>gofmt</code> (đọc là gâu phằm) - chương trình này sẽ
format tất cả code trong thư mục về một chuẩn mà nó đã quy định. Ban đầu,
người ta vẫn còn tranh cãi về việc bị ép style, nhưng rồi sau một hồi, lợi ích
của mỗi cái tôi đã sụp đổ trước lợi ích tập thể mà <code>gofmt</code> mang lại: mọi đoạn
code đều trông giống nhau, khiến style không còn gì để tranh cãi, lập trình viên
nhìn code của thằng kia cũng giống như của mình, dễ đọc - dễ hiểu hơn, expert
hay newbie đều chung 1 style cả.</p>
<p><code>gofmt</code> không phải là chương trình đầu tiên làm vậy, trước đây, trong cộng
đồng Python đã xuất hiện 1 chương trình tên <code>autopep8</code> hay Google cũng có
<a href="https://github.com/google/yapf">YAPF</a>. Nhưng <code>Go</code> là ngôn ngữ đầu tiên mang
code formatter vào chính thống, chính thức chấm dứt cuộc chiến vô bổ về style
kéo dài vài thập kỷ. Để rồi từ đó, các ngôn ngữ lập trình khác đua nhau học theo
như <a href="https://github.com/rust-lang/rustfmt">Rust</a>,
<a href="https://elixir-lang.org/blog/2018/01/17/elixir-v1-6-0-released/">Elixir</a>
và Python thì có <a href="https://github.com/psf/black">black</a>.</p>
<p>Không có giải pháp nào để giải quyết 1 vấn đề tốt hơn là làm cho nó biến mất.</p>
<h2>black là gì</h2>
<p>Black là một câu lệnh cài bằng pip: <code>pip install black</code>, yêu cầu Python3.6 trở
lên mới chạy. <code>black</code> xuất hiện như một project của một lập trình viên nào đó
trên in tơ nét, sau vài năm trở nên cực kỳ phổ biến, và giờ đã chính thức
được nằm dưới mái nhà PSF (Python Software Foundation) cùng với <code>requests</code>.</p>
<p><a href="https://github.com/psf/black/blob/master/black.py">https://github.com/psf/black/blob/master/black.py</a></p>
<p>Code của black chỉ vọn vẹn 4000 dòng, sử dụng các tính năng mới nhất của Python
như f-string, type annotation, asyncio...
(vì thế nên yêu cầu Python3.6+ để chạy, mặc dù vẫn có thể format code 2.7)</p>
<h2>Dùng black để format code Python</h2>
<p>Ví dụ có 1 file foo.py</p>
<div class="highlight"><pre><span></span><code><span class="nv">def</span> <span class="nv">sum_two</span><span class="ss">(</span><span class="nv">a</span>,<span class="nv">b</span><span class="ss">)</span>:
<span class="nv">c</span><span class="o">=</span> <span class="nv">a</span> <span class="o">+</span> <span class="nv">b</span>
<span class="k">return</span> <span class="nv">c</span>
</code></pre></div>
<p>Nếu thành thạo PEP8, thấy ngay có 4 chỗ phải sửa ở đây:
<code>a,b</code> thiếu dấu space sau <code>,</code>, <code>c</code> thiếu dấu space theo sau, sau <code>a</code> thừa 1
space, thừa 1 dòng
trống trước return. Vậy chỉ 3 dòng code, người review phải gõ ra 4 "vấn đề"
về style, và người code ra 3 dòng này, khi đọc review cũng chẳng vui vẻ gì,
kể cả người ta nói đúng.</p>
<p>Chạy:</p>
<div class="highlight"><pre><span></span><code>$ black foo.py
reformatted foo.py
All <span class="k">done</span>! ✨ 🍰 ✨
<span class="m">1</span> file reformatted.
$ cat foo.py
def sum_two<span class="o">(</span>a, b<span class="o">)</span>:
<span class="nv">c</span> <span class="o">=</span> a + b
<span class="k">return</span> c
</code></pre></div>
<p>Đẹp, chuẩn, ngon! Không còn gì mong đợi thêm.
black có nhiều option để chỉnh style cho phù hợp với tiêu chuẩn của bạn,
hay dùng nhất là để set độ dài của 1 dòng, vốn là 79 ký tự theo chuẩn PEP8,
thì black mặc định là 88:</p>
<div class="highlight"><pre><span></span><code> -l, --line-length INTEGER How many characters per line to allow.
<span class="o">[</span>default: <span class="m">88</span><span class="o">]</span>
</code></pre></div>
<p>Bạn có thể gọi <code>black -l79 .</code> để theo PEP8.</p>
<h2>Hành động của chúng ta</h2>
<p>Đã đến lúc để quên đi việc format code bằng tay, nhớ vài chục tiêu chuẩn
của PEP8, format code hãy để cho đen (black) không vâu lo - việc này để
đen không vâu lo.
Thêm dòng này vào <code>Makefile</code> của bạn:</p>
<div class="highlight"><pre><span></span><code><span class="n">fmt</span><span class="o">:</span>
<span class="n">black</span> <span class="o">-</span><span class="n">l79</span> <span class="o">.</span>
</code></pre></div>
<p>hay cài đặt text editor/IDE tự động chạy <code>black</code> sau mỗi lần save code.</p>
<p>Thêm dòng sau vào CI để bắt quả tang thằng nào không dùng black:</p>
<div class="highlight"><pre><span></span><code>black --check .
</code></pre></div>
<p>Black sẽ thông báo các file chưa chuẩn format:</p>
<div class="highlight"><pre><span></span><code>$ black --check .
would reformat /home/hvn/me/people/content/mypy_simple.py
would reformat /home/hvn/me/people/publishconf.py
would reformat /home/hvn/me/people/pelicanconf.py
would reformat /home/hvn/me/people/fabfile.py
Oh no! 💥 💔 💥
<span class="m">4</span> files would be reformatted, <span class="m">1</span> file would be left unchanged.
</code></pre></div>
<h2>Kết luận</h2>
<p>Hãy dùng <code>black</code>! Và đừng quên share bài viết này, để cộng đồng code Python
không còn mất thời gian ít bổ.</p>Mypy - là trai hay là gái?2020-03-10T00:00:00+07:002020-03-10T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-03-10:/article/mypy/<p>Là bạn hay là bè? là str hay int? Thêm type cho Python code mà không ho, không sốt, không toang.</p><p>Vài ba năm gần đây, làng Vũ Trụ rộn lên trào lưu thêm "type" vào các ngôn ngữ lập trình dynamic typing.</p>
<ul>
<li><a href="https://www.php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration">PHP</a></li>
<li><a href="https://www.typescriptlang.org/">JavaScript|TypeScript</a></li>
</ul>
<p>Python đã già nhưng vẫn đú, cũng không bỏ lỡ cuộc đu trend này.
Kết quả là ngày hôm nay, bạn đã có thể thêm type vào code Python -
nếu muốn - như <a href="https://blogs.dropbox.com/tech/2019/09/our-journey-to-type-checking-4-million-lines-of-python/">DropBox</a>, <a href="https://instagram-engineering.com/let-your-code-type-hint-itself-introducing-open-source-monkeytype-a855c7284881">Instagram</a>... . Python chính thức thêm <a href="https://www.python.org/dev/peps/pep-0484/">type hint/type annotation</a>
vào từ bản 3.5,
chú ý rằng type này không ảnh hưởng/giúp đỡ gì bạn, nếu không sử dụng mypy hay
các IDE.</p>
<p>Mypy là static type checker - tool giúp phân tích code (static analysis) dựa trên type annotation, dự
án có sự tham gia của tác giả Python - Guido van Rossum, ... và cả <a href="https://github.com/python/mypy/pull/4594">HVN</a></p>
<h2>Thêm type để làm gì?</h2>
<p>Các ngôn ngữ lập trình dynamic typing: Python, JavaScript, PHP, Ruby được
ưa chuộng và luôn chiếm vị trí top các bảng xếp hạng ngôn ngữ lập trình trong
những năm gần đây, hay Clojure, LISP, Erlang, Elixir... dù không lên đỉnh nhưng
vẫn luôn hot.
Dynamic typing giúp code trở nên ngắn gọn và linh hoạt.
Vậy thêm type (kiểu) làm gì? không lẽ để dài hơn và bớt linh hoạt hơn?</p>
<p>Code dài dòng thêm thì rõ là không ai muốn, bởi nếu muốn đã quay về viết Java
hết rồi, nhưng bớt linh hoạt hơn là một mục đích đáng xem xét.</p>
<p>Khi mọi thứ linh hoạt, nếu không tuân theo các quy tắc, không có kỷ luật cá nhân
tốt, sẽ dẫn tới rối loạn. Hoặc khi hệ thống trở nên phức tạp hơn,
nhiều tính năng phụ thuộc vào nhau, cũng dẫn tới việc không kiểm soát được.</p>
<p>Điều này sẽ dễ thấy hơn khi tham gia một dự án có nhiều lập trình viên.
Một team 5 Python dev làm việc đã quen với nhau, code
cùng chuẩn PEP8, cùng không thích OOP, ... hay nói cách khác là một team thực
thụ, sẽ code nhanh như tên lửa, vài giờ một tính năng, bay vèo vèo như
<a href="https://books.google.com.vn/books?id=eulODwAAQBAJ&lpg=PA136&dq=google+video+vs+youtube+python+story&pg=PA136&redir_esc=y#v=onepage&q=google%20video%20vs%20youtube%20python%20story&f=false">YouTube dev</a> .</p>
<p>Một nhóm người khác với 5 lập trình viên, học
lập trình từ các nguồn khác nhau, trình độ khác nhau, thậm chí ngôn ngữ thành
thạo cũng khác nhau, nếu làm cùng một dự án sẽ rất rối loạn.
Có chỗ viết 3 class để gọi 1 function cho đúng chuẩn kế thừa, OOP của Java,
có function viết theo kiểu recursive, có chỗ đặt tên biến một chữ cái,
không viết function, một hàm main dài hàng trăm dòng,
function mỗi nhánh trả về một kiểu khác, viết decorator chỉ để dùng 1 lần và
thể hiện, sửa một function rồi các function khác hỏng theo... và hàng trăm thứ
khác có thể sai hơn nữa. Một team như vậy phát
triển sẽ rất chậm, nhiều bug, khó thêm tính năng,
thậm chí gây mệt mỏi, stress khi phải làm việc với nhau. Giải pháp thì lại
không thể là giải tán, cãi nhau, vậy làm gì?</p>
<p>Type là một phần giải pháp, type giúp đặt ràng buộc rõ ràng đầu vào đầu ra,
đảm bảo một function luôn trả về cùng 1 kiểu dù ở nhánh nào. Function
là kiến trúc cơ bản của 1 chương trình, một hệ thống. Khi function định nghĩa
rõ ràng, các bên tương tác (gọi function) với nhau cũng sẽ rõ ràng.
Nhân viên mới tuyển, sinh viên mới ra trường,
vào sửa function trả về nhầm kiểu sẽ bị type bắt lại ngay.</p>
<ul>
<li>Type là một thứ CÔNG CỤ giúp giảm sự linh hoạt của code, tăng thêm kỷ luật,
đảm bảo code ít bị rối loạn hơn. Nếu code 1 mình, hay bạn chắc chăn mình và
<strong>các đồng nghiệp</strong> đủ kỷ luật để không viết function trả về các kiểu khác nhau
thì type cũng không cần thiết.</li>
<li>Type không nên là thứ can thiệp/cản trở nhiều vào mục đích của lập trình viên.
Ta muốn có công cụ trợ giúp, chứ không muốn nó chống lại mình. Type dài dòng
như của Java là một ví dụ điển hình khiến việc viết code cũng trở nên ngại.</li>
</ul>
<h2>Sử dụng mypy</h2>
<h3>Cài đặt</h3>
<div class="highlight"><pre><span></span><code>pip install mypy
</code></pre></div>
<h4>Ví dụ 1 - đơn giản để bắt đầu</h4>
<p>Ví dụ 1: đoạn code không có type:</p>
<p>File <code>mypy_simple.py</code></p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">sum_of_three</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">,</span> <span class="n">c</span><span class="p">):</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span> <span class="o">+</span> <span class="n">c</span>
<span class="k">return</span> <span class="n">result</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">sum_of_three</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span>
<span class="n">message</span> <span class="o">=</span> <span class="s2">"The answer of life: "</span> <span class="o">+</span> <span class="n">result</span>
<span class="nb">print</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>Chạy mypy:</p>
<div class="highlight"><pre><span></span><code>$ mypy mypy_simple.py
Success: no issues found <span class="k">in</span> <span class="m">1</span> <span class="nb">source</span> file
</code></pre></div>
<p>Không có gì xảy ra, do không function/name nào có type annotation cả.
Mặc định này giúp việc thêm type là tùy ý. Team có thể có người viết type,
có người không ở các function khác nhau, đều OK. Nếu cực nghiêm khắc,
có thể bật chế độ strict lên:</p>
<div class="highlight"><pre><span></span><code>$ mypy --strict mypy_simple.py
mypy_simple.py:1: error: Function is missing a <span class="nb">type</span> annotation
mypy_simple.py:5: error: Function is missing a <span class="k">return</span> <span class="nb">type</span> annotation
mypy_simple.py:5: note: Use <span class="s2">"-> None"</span> <span class="k">if</span> <span class="k">function</span> does not <span class="k">return</span> a value
mypy_simple.py:6: error: Call to untyped <span class="k">function</span> <span class="s2">"sum_of_three"</span> <span class="k">in</span> typed context
mypy_simple.py:10: error: Call to untyped <span class="k">function</span> <span class="s2">"main"</span> <span class="k">in</span> typed context
Found <span class="m">4</span> errors <span class="k">in</span> <span class="m">1</span> file <span class="o">(</span>checked <span class="m">1</span> <span class="nb">source</span> file<span class="o">)</span>
</code></pre></div>
<p>Ở chế độ này, mọi thứ thiếu type annotation đều bị thông báo, đây chỉ là ví dụ
cực đoan, phải tốn khá nhiều công sức và làm quen mới có thể bật chế độ này lên,
vậy nên khó quá tạm thời bỏ qua.</p>
<p>Thêm type cho đoạn code trong ví dụ 1:
với Python type annotation, ta thường chỉ thêm cho định nghĩa của các function,
ít khi phải khai báo cho các variable. Ví dụ trên có 2 function, sau khi thêm
type vào <code>sum_of_three</code> sẽ trông như sau:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">sum_of_three</span><span class="p">(</span><span class="n">a</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">b</span><span class="p">:</span> <span class="nb">int</span><span class="p">,</span> <span class="n">c</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">int</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span> <span class="o">+</span> <span class="n">c</span>
<span class="k">return</span> <span class="n">result</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">sum_of_three</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span>
<span class="n">message</span> <span class="o">=</span> <span class="s2">"The answer of life: </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</code></pre></div>
<p>3 argument đều có kiểu là <code>: int</code>, <code>-> int</code> nói rằng function này
trả về kiểu <code>int</code>.
Chạy lại lệnh mypy</p>
<div class="highlight"><pre><span></span><code>$ mypy mypy_simple.py
Success: no issues found <span class="k">in</span> <span class="m">1</span> <span class="nb">source</span> file
</code></pre></div>
<p>Vẫn trông như không có gì xảy ra, nhưng thật ra là có, do code của ta không
có vấn đề gì nên mypy cũng không báo gì. Thử đổi trong function <code>main</code>,
cộng kết quả của <code>sum_of_three</code> (kiểu int) với một <code>str</code>:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">sum_of_three</span><span class="p">(</span><span class="mi">6</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span> <span class="o">*</span> <span class="mi">2</span>
<span class="n">message</span> <span class="o">=</span> <span class="s2">"The answer of life: "</span> <span class="o">+</span> <span class="n">result</span>
<span class="nb">print</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
</code></pre></div>
<p>Nếu là lập trình viên JavaScript, bạn sẽ mong đợi một kết quả str bình thường,
<code>"The answer of life: 42"</code>, bởi JavaScript thuộc loại weak typing,
còn Python là strong typing: int là int, str là str, không cộng trừ lẫn lộn.</p>
<p>Nếu là một PyMier chân chính, bạn sẽ nhận ra ngay đoạn code này gặp exception
khi CHẠY THẬT, do cộng một str với một số int.</p>
<div class="highlight"><pre><span></span><code>$ python mypy_simple.py
Traceback <span class="o">(</span>most recent call last<span class="o">)</span>:
File <span class="s2">"mypy_simple.py"</span>, line <span class="m">12</span>, <span class="k">in</span> <module>
main<span class="o">()</span>
File <span class="s2">"mypy_simple.py"</span>, line <span class="m">8</span>, <span class="k">in</span> main
<span class="nv">message</span> <span class="o">=</span> <span class="s2">"The answer of life: "</span> + result
TypeError: must be str, not int
Command exited with non-zero status <span class="m">1</span>
</code></pre></div>
<p>Vậy ta chỉ biết, khi chạy thật, mà lúc ấy mới biết thì "toang" rồi.
Có cách nào <strong>biết trước</strong> khi chạy không? Mypy sẽ giúp làm chuyện ấy:</p>
<div class="highlight"><pre><span></span><code>$ mypy mypy_simple.py
mypy_simple.py:8: error: Unsupported operand types <span class="k">for</span> + <span class="o">(</span><span class="s2">"str"</span> and <span class="s2">"int"</span><span class="o">)</span>
Found <span class="m">1</span> error <span class="k">in</span> <span class="m">1</span> file <span class="o">(</span>checked <span class="m">1</span> <span class="nb">source</span> file<span class="o">)</span>
</code></pre></div>
<p>Chú ý: việc phân tích function main chỉ xảy ra khi nó có type annotation:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
</code></pre></div>
<p>Function main không nhận argument, trả về kiểu None. Nếu bỏ <code>-> None</code> đi,
mypy sẽ không kiểm tra function main.</p>
<h4>Ví dụ 2 - các containers: list, tuple, dict, set</h4>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span><span class="p">,</span> <span class="n">Tuple</span>
<span class="k">def</span> <span class="nf">with_index</span><span class="p">(</span><span class="n">names</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="n">Tuple</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span> <span class="nb">str</span><span class="p">]]:</span>
<span class="k">return</span> <span class="p">[(</span><span class="n">idx</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span> <span class="k">for</span> <span class="n">idx</span><span class="p">,</span> <span class="n">name</span> <span class="ow">in</span> <span class="nb">enumerate</span><span class="p">(</span><span class="n">names</span><span class="p">)]</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">()</span> <span class="o">-></span> <span class="kc">None</span><span class="p">:</span>
<span class="n">result</span> <span class="o">=</span> <span class="mi">21</span> <span class="o">*</span> <span class="mi">2</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">with_index</span><span class="p">([</span><span class="s2">"Corona"</span><span class="p">,</span> <span class="s2">"Tiger Nau"</span><span class="p">,</span> <span class="s2">"TrucBach"</span><span class="p">])</span>
<span class="nb">print</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>Chỉ có str, int, float, bool, None là các kiểu dùng ngay.
Với list, tuple, set, dict, cần phải import type tương ứng
từ standard lib <code>typing</code>, các type viết Hoa chữ cái đầu.
Có thể khai báo qua loa
<code>def with_index(names: List) -> List:</code>
hoặc chi tiết như trong ví dụ 2.</p>
<div class="highlight"><pre><span></span><code>$ mypy mypy_simple.py
mypy_simple.py:11: error: Incompatible types <span class="k">in</span> assignment <span class="o">(</span>expression has <span class="nb">type</span> <span class="s2">"List[Tuple[int, str]]"</span>, variable has <span class="nb">type</span> <span class="s2">"int"</span><span class="o">)</span>
</code></pre></div>
<p>Lỗi này thường gặp, do chuyện dùng chung tên <code>result</code>,
mypy lần đầu gặp sẽ nghĩ <code>result</code> là kiểu <code>int</code>, qua dòng tiếp
theo lại suy luận nó được gán giá trị kiểu <code>List[Tuple[int, str]]</code>. Việc dùng
một biến để chỉ tới nhiều kiểu khác nhau không hiếm trong dynamic typing, nhưng
là chuyện không thể trong các ngôn ngữ static typing. Cách giải quyết chuẩn
nhất là đổi tên biến.</p>
<h4>Ví dụ 3 - các object phức tạp, thư viện bên ngoài</h4>
<p>Với các lập trình viên Python, type là một thứ lạ, nên không phải dẽ gì ngồi
đọc ngay ra kiểu của <code>resp</code> sau đây là gì mà gõ vào:</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Any</span>
<span class="kn">import</span> <span class="nn">requests</span>
<span class="k">def</span> <span class="nf">process_response</span><span class="p">(</span><span class="n">resp</span><span class="p">:</span> <span class="n">Any</span><span class="p">,</span> <span class="n">msg</span><span class="p">:</span> <span class="nb">str</span> <span class="o">=</span> <span class="s1">'From: '</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span>
<span class="k">return</span> <span class="n">msg</span> <span class="o">+</span> <span class="n">resp</span><span class="o">.</span><span class="n">json</span><span class="p">()[</span><span class="s1">'origin'</span><span class="p">]</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">requests</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'https://httpbin.org/ip'</span><span class="p">)</span>
<span class="n">output</span> <span class="o">=</span> <span class="n">process_response</span><span class="p">(</span><span class="n">r</span><span class="p">)</span><span class="c1"># + 10</span>
<span class="nb">print</span><span class="p">(</span><span class="n">output</span><span class="p">)</span>
</code></pre></div>
<p><code>Any</code> giúp điền vào chỗ trống khi không biết kiểu gì, đồng nghĩa
với việc mất đi một chút bảo vệ của mypy do mypy sẽ cho phép gọi
function <code>process_response</code> với argument đầu tiên thuộc bất kỳ kiểu nào.</p>
<p><code>msg: str = 'From: '</code> là argument <code>msg</code>, kiểu <code>str</code>, với giá trị
default <code>From:</code>.</p>
<div class="highlight"><pre><span></span><code>$ python mypy_simple.py
From: <span class="m">111</span>.212.107.29
</code></pre></div>
<p>Có một cách khác để nhờ mypy tìm giúp kiểu của <code>resp</code>, đó là khai báo sai kiểu,
thay <code>Any</code> thành <code>int</code>, chạy mypy sẽ thấy:</p>
<div class="highlight"><pre><span></span><code>$ mypy mypy_simple.py
mypy_simple.py:5: error: <span class="s2">"int"</span> has no attribute <span class="s2">"json"</span>
mypy_simple.py:8: error: Argument <span class="m">1</span> to <span class="s2">"process_response"</span> has incompatible <span class="nb">type</span> <span class="s2">"Response"</span><span class="p">;</span> expected <span class="s2">"int"</span>
Found <span class="m">2</span> errors <span class="k">in</span> <span class="m">1</span> file <span class="o">(</span>checked <span class="m">1</span> <span class="nb">source</span> file<span class="o">)</span>
</code></pre></div>
<p>Vậy kiểu của <code>resp</code> cần khai báo là Response, đầy đủ là <code>requests.models.Response</code>
có thể tìm ra bằng cách <code>print(type(r))</code></p>
<h4>Ví dụ 4 - function nhận vào nhiều kiểu</h4>
<p>Cũng có lúc, ta muốn chủ ý nhận vào nhiều loại input khác kiểu,
nhưng chỉ giới hạn trong 1 nhóm, ví dụ như int, str, float, chứ không phải dict
hay list, nếu dùng <code>Any</code> thì dễ dãi quá:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">any_number</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="nb">int</span><span class="p">)</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span>
<span class="k">return</span> <span class="s2">"This is </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="n">any_number</span><span class="p">(</span><span class="mi">6</span><span class="p">)</span>
<span class="n">any_number</span><span class="p">(</span><span class="s2">"9"</span><span class="p">)</span>
<span class="c1">### mypy check</span>
<span class="n">error</span><span class="p">:</span> <span class="n">Argument</span> <span class="mi">1</span> <span class="n">to</span> <span class="s2">"any_number"</span> <span class="n">has</span> <span class="n">incompatible</span> <span class="nb">type</span> <span class="s2">"str"</span><span class="p">;</span> <span class="n">expected</span> <span class="s2">"int"</span>
</code></pre></div>
<p>Giải pháp là dùng Union.</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">Union</span>
<span class="k">def</span> <span class="nf">any_number</span><span class="p">(</span><span class="n">n</span><span class="p">:</span> <span class="n">Union</span><span class="p">[</span><span class="nb">int</span><span class="p">,</span><span class="nb">float</span><span class="p">,</span><span class="nb">str</span><span class="p">])</span> <span class="o">-></span> <span class="nb">str</span><span class="p">:</span>
</code></pre></div>
<p>Các kiểu type khác: <a href="https://mypy.readthedocs.io/en/stable/kinds_of_types.html">https://mypy.readthedocs.io/en/stable/kinds_of_types.html</a> đáng chú ý như Optional khi có thể trả về None, hay Callable là kiểu cho
function.</p>
<h3>Stub</h3>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">boto3</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="nv">mypy</span> <span class="nv">mypy_simple</span>.<span class="nv">py</span>
<span class="nv">mypy_simple</span>.<span class="nv">py</span>:<span class="mi">1</span>: <span class="nv">error</span>: <span class="nv">No</span> <span class="nv">library</span> <span class="nv">stub</span> <span class="nv">file</span> <span class="k">for</span> <span class="nv">module</span> <span class="s1">'</span><span class="s">boto3</span><span class="s1">'</span>
<span class="nv">mypy_simple</span>.<span class="nv">py</span>:<span class="mi">1</span>: <span class="nv">note</span>: <span class="ss">(</span><span class="nv">Stub</span> <span class="nv">files</span> <span class="nv">are</span> <span class="nv">from</span> <span class="nv">https</span>:<span class="o">//</span><span class="nv">github</span>.<span class="nv">com</span><span class="o">/</span><span class="nv">python</span><span class="o">/</span><span class="nv">typeshed</span><span class="ss">)</span>
</code></pre></div>
<p>Sau khi Python chính thức thêm type notation, không có nghĩa là các core developer sẽ đi sửa hàng loạt các file thư viện đã chạy ổn định vài chục năm để
thêm type. Thay vào đó, họ tạo ra các stub file.
stub file là file chứa type annotation của các function trong library. Nó tương tự header file trong C/C++.</p>
<p>Ví dụ <a href="https://github.com/python/typeshed/blob/master/stdlib/3/itertools.pyi">itertools</a></p>
<p>Các stub file của Python standard lib được gom lại tại <a href="https://github.com/python/typeshed">python/typeshed</a>.</p>
<p>Các thư viện bên ngoài thường ít khi có sẵn stub file,
Ví dụ như <code>boto3</code> - thư viện cực kỳ phổ biến - và chính thức để làm việc với
API của AWS, tới nay vẫn chưa có stub file chính thức từ AWS.
Nên để đơn giản, thay vì phải chiến đấu với type, ta có thể bỏ qua:</p>
<div class="highlight"><pre><span></span><code><span class="err">$</span> <span class="n">mypy</span> <span class="o">--</span><span class="n">ignore</span><span class="o">-</span><span class="n">missing</span><span class="o">-</span><span class="kn">import</span> <span class="nn">.</span>
</code></pre></div>
<p>hay tự tạo stub như <a href="https://github.com/htlcnn/autocad_objectarx_python_stubs">htlcnn</a>.
Xem hướng dẫn tự tạo stub file tại <a href="https://github.com/python/mypy/wiki/Creating-Stubs-For-Python-Modules">mypy wiki</a></p>
<h3>Những vấn đề type giúp giải quyết</h3>
<ul>
<li>Kiểm tra kiểu, đảm bảo function trả về thống nhất một kiểu, nhận được đúng
kiểu đầu vào.</li>
<li>Giúp nhìn vào dòng def (thuật ngữ chính xác: function signature) là biết luôn
cần gọi với argument nào, trả về kiểu gì.</li>
</ul>
<h3>Những vấn đề type KHÔNG giúp giải quyết</h3>
<ul>
<li>Thay cho unittest: unittest dùng để kiểm tra logic chứ không phải để kiểm tra kiểu. Do không có ràng buộc về kiểu, nên trong unittest thường kiểm tra cả kiều của đầu ra function, việc này hoàn toàn bị loại bỏ khi dùng mypy.</li>
<li>Làm việc nhóm không hiệu quả: type chỉ là một phần giải pháp giúp xóa bớt sự
chênh lệch trình độ giữa các lập trình viên. Bạn nên tìm cách đào tạo, chia sẻ
kinh nghiệm, luyện tập cùng các đồng nghiệp thì hơn.</li>
</ul>
<h3>Hành động của chúng ta</h3>
<p>Thêm ngay dòng sau vào Makefile, hay hệ thống CI của bạn, ngay sau pep8, flake8 hay pylint:</p>
<div class="highlight"><pre><span></span><code><span class="n">mypy</span> <span class="o">--</span><span class="n">ignore</span><span class="o">-</span><span class="n">missing</span><span class="o">-</span><span class="kn">import</span> <span class="nn">.</span>
</code></pre></div>
<h2>Kết luận</h2>
<p>Type là một công cụ tốt cho các Pythonista như pylint pep8, giúp phát triển các dự án lớn hơn,
nhiều người tham gia một cách dễ dàng hơn, trong khi việc đầu tư thì không có
gì to tát cả - chỉ việc share bài viết này.</p>C thật là đơn giản2020-02-19T00:00:00+07:002020-02-19T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-02-19:/article/cde/<p>C đơn giản hơn Python?</p><p>Đơn giản, nhanh, chậm, xinh, cao, thấp, giỏi, xịn... đều là những khái niệm
mang tính chất tương đối. Có cái hơn khi so sánh ở góc độ này, nhưng lại kém
khi so sánh ở góc độ khác. Trong ngành lập trình, mọi thứ đều là sự đánh đổi
(trade off),
không có giải pháp nào thỏa mãn tất cả mọi nhu cầu (silver bullet) - hoặc có
nhưng chưa ai tìm ra.</p>
<p>Không cần bàn cãi, ai cũng đồng ý code Python dễ đọc, viết hơn C, hay... đơn
giản hơn. Nhưng cái đơn giản đó, là đơn giản với con người, với lập trình viên,
còn với máy tính thì hoàn toàn ngược lại.</p>
<p>Ta sẽ thử nghiệm chương trình đơn giản nhất trái đất: hello world viết bằng C
và Python rồi so sánh dùng <code>strace</code> - một công cụ debug "cao cấp" thường
dùng bởi các SysAdmin.</p>
<h3>strace</h3>
<div class="highlight"><pre><span></span><code>$ whatis strace
strace <span class="o">(</span><span class="m">1</span><span class="o">)</span> - trace system calls and signals
</code></pre></div>
<p>Bài viết thực hiện trên Ubuntu 18.04, <code>cc</code> - C compiler có lẽ là có sẵn.
Hoặc nếu không có, hãy cài bằng <code>sudo apt-get install -y build-essential strace</code></p>
<h3>C Programming language</h3>
<p>4 dòng code C</p>
<div class="highlight"><pre><span></span><code><span class="cp">#include</span> <span class="cpf"><stdio.h></span><span class="cp"></span>
<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">void</span><span class="p">)</span> <span class="p">{</span>
<span class="n">puts</span><span class="p">(</span><span class="s">"Hello world!"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div>
<p>Compile rồi chạy - yeah, cực đơn giản, không cần gì khác cả, cũng không cần
làm việc đơn giản này trở thành rắc rôi.</p>
<div class="highlight"><pre><span></span><code>$ cc hello.c -o hello <span class="c1"># compile, sinh ra file hello</span>
$ ./hello <span class="c1"># chạy file hello</span>
Hello world!
</code></pre></div>
<p>Giờ chạy với <code>strace</code> để xem chương trình siêu đơn giản này gọi những system call
nào, <code>-C</code> sẽ hiển thị bảng thống kê, <code>-S calls</code> sẽ sắp xếp thống kê này
theo cột <code>calls</code>, giảm dần.</p>
<div class="highlight"><pre><span></span><code>$ strace -CScalls ./hello
execve<span class="o">(</span><span class="s2">"./hello"</span>, <span class="o">[</span><span class="s2">"./hello"</span><span class="o">]</span>, 0x7fff1e9af718 /* <span class="m">68</span> vars */<span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
brk<span class="o">(</span>NULL<span class="o">)</span> <span class="o">=</span> 0x56174aa4e000
access<span class="o">(</span><span class="s2">"/etc/ld.so.nohwcap"</span>, F_OK<span class="o">)</span> <span class="o">=</span> -1 ENOENT <span class="o">(</span>No such file or directory<span class="o">)</span>
access<span class="o">(</span><span class="s2">"/etc/ld.so.preload"</span>, R_OK<span class="o">)</span> <span class="o">=</span> -1 ENOENT <span class="o">(</span>No such file or directory<span class="o">)</span>
openat<span class="o">(</span>AT_FDCWD, <span class="s2">"/etc/ld.so.cache"</span>, O_RDONLY<span class="p">|</span>O_CLOEXEC<span class="o">)</span> <span class="o">=</span> <span class="m">3</span>
fstat<span class="o">(</span><span class="m">3</span>, <span class="o">{</span><span class="nv">st_mode</span><span class="o">=</span>S_IFREG<span class="p">|</span><span class="m">0644</span>, <span class="nv">st_size</span><span class="o">=</span><span class="m">106420</span>, ...<span class="o">})</span> <span class="o">=</span> <span class="m">0</span>
mmap<span class="o">(</span>NULL, <span class="m">106420</span>, PROT_READ, MAP_PRIVATE, <span class="m">3</span>, <span class="m">0</span><span class="o">)</span> <span class="o">=</span> 0x7f867f8fc000
close<span class="o">(</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
access<span class="o">(</span><span class="s2">"/etc/ld.so.nohwcap"</span>, F_OK<span class="o">)</span> <span class="o">=</span> -1 ENOENT <span class="o">(</span>No such file or directory<span class="o">)</span>
openat<span class="o">(</span>AT_FDCWD, <span class="s2">"/lib/x86_64-linux-gnu/libc.so.6"</span>, O_RDONLY<span class="p">|</span>O_CLOEXEC<span class="o">)</span> <span class="o">=</span> <span class="m">3</span>
read<span class="o">(</span><span class="m">3</span>, <span class="s2">"\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"</span>..., <span class="m">832</span><span class="o">)</span> <span class="o">=</span> <span class="m">832</span>
fstat<span class="o">(</span><span class="m">3</span>, <span class="o">{</span><span class="nv">st_mode</span><span class="o">=</span>S_IFREG<span class="p">|</span><span class="m">0755</span>, <span class="nv">st_size</span><span class="o">=</span><span class="m">2030544</span>, ...<span class="o">})</span> <span class="o">=</span> <span class="m">0</span>
mmap<span class="o">(</span>NULL, <span class="m">8192</span>, PROT_READ<span class="p">|</span>PROT_WRITE, MAP_PRIVATE<span class="p">|</span>MAP_ANONYMOUS, -1, <span class="m">0</span><span class="o">)</span> <span class="o">=</span> 0x7f867f8fa000
mmap<span class="o">(</span>NULL, <span class="m">4131552</span>, PROT_READ<span class="p">|</span>PROT_EXEC, MAP_PRIVATE<span class="p">|</span>MAP_DENYWRITE, <span class="m">3</span>, <span class="m">0</span><span class="o">)</span> <span class="o">=</span> 0x7f867f2fe000
mprotect<span class="o">(</span>0x7f867f4e5000, <span class="m">2097152</span>, PROT_NONE<span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
mmap<span class="o">(</span>0x7f867f6e5000, <span class="m">24576</span>, PROT_READ<span class="p">|</span>PROT_WRITE, MAP_PRIVATE<span class="p">|</span>MAP_FIXED<span class="p">|</span>MAP_DENYWRITE, <span class="m">3</span>, 0x1e7000<span class="o">)</span> <span class="o">=</span> 0x7f867f6e5000
mmap<span class="o">(</span>0x7f867f6eb000, <span class="m">15072</span>, PROT_READ<span class="p">|</span>PROT_WRITE, MAP_PRIVATE<span class="p">|</span>MAP_FIXED<span class="p">|</span>MAP_ANONYMOUS, -1, <span class="m">0</span><span class="o">)</span> <span class="o">=</span> 0x7f867f6eb000
close<span class="o">(</span><span class="m">3</span><span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
arch_prctl<span class="o">(</span>ARCH_SET_FS, 0x7f867f8fb4c0<span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
mprotect<span class="o">(</span>0x7f867f6e5000, <span class="m">16384</span>, PROT_READ<span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
mprotect<span class="o">(</span>0x56174a941000, <span class="m">4096</span>, PROT_READ<span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
mprotect<span class="o">(</span>0x7f867f916000, <span class="m">4096</span>, PROT_READ<span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
munmap<span class="o">(</span>0x7f867f8fc000, <span class="m">106420</span><span class="o">)</span> <span class="o">=</span> <span class="m">0</span>
fstat<span class="o">(</span><span class="m">1</span>, <span class="o">{</span><span class="nv">st_mode</span><span class="o">=</span>S_IFCHR<span class="p">|</span><span class="m">0620</span>, <span class="nv">st_rdev</span><span class="o">=</span>makedev<span class="o">(</span><span class="m">136</span>, <span class="m">5</span><span class="o">)</span>, ...<span class="o">})</span> <span class="o">=</span> <span class="m">0</span>
brk<span class="o">(</span>NULL<span class="o">)</span> <span class="o">=</span> 0x56174aa4e000
brk<span class="o">(</span>0x56174aa6f000<span class="o">)</span> <span class="o">=</span> 0x56174aa6f000
write<span class="o">(</span><span class="m">1</span>, <span class="s2">"Hello world!\n"</span>, 13Hello world!
<span class="o">)</span> <span class="o">=</span> <span class="m">13</span>
exit_group<span class="o">(</span><span class="m">0</span><span class="o">)</span> <span class="o">=</span> ?
+++ exited with <span class="m">0</span> +++
% <span class="nb">time</span> seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">5</span> mmap
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">4</span> mprotect
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">3</span> fstat
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">3</span> brk
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">3</span> <span class="m">3</span> access
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">2</span> close
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">2</span> openat
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> <span class="nb">read</span>
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> write
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> munmap
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> execve
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> arch_prctl
------ ----------- ----------- --------- --------- ----------------
<span class="m">100</span>.00 <span class="m">0</span>.000000 <span class="m">27</span> <span class="m">3</span> total
</code></pre></div>
<p>Có tổng cộng 27 syscall được thực hiện, 3 fail.</p>
<h3>Python</h3>
<p>Một chương trình Python 3.6 in ra màn hình dòng chữ <code>hello world</code> tương tự:</p>
<div class="highlight"><pre><span></span><code>$ strace -cScalls python3 -c <span class="s1">'print("Hello world!")'</span>
Hello world!
% <span class="nb">time</span> seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
<span class="m">9</span>.22 <span class="m">0</span>.000277 <span class="m">2</span> <span class="m">166</span> <span class="m">32</span> stat
<span class="m">7</span>.96 <span class="m">0</span>.000239 <span class="m">3</span> <span class="m">94</span> fstat
<span class="m">7</span>.19 <span class="m">0</span>.000216 <span class="m">3</span> <span class="m">79</span> <span class="nb">read</span>
<span class="m">7</span>.19 <span class="m">0</span>.000216 <span class="m">3</span> <span class="m">68</span> rt_sigaction
<span class="m">8</span>.09 <span class="m">0</span>.000243 <span class="m">4</span> <span class="m">58</span> close
<span class="m">10</span>.25 <span class="m">0</span>.000308 <span class="m">5</span> <span class="m">57</span> <span class="m">2</span> openat
<span class="m">1</span>.63 <span class="m">0</span>.000049 <span class="m">1</span> <span class="m">43</span> <span class="m">6</span> lseek
<span class="m">17</span>.78 <span class="m">0</span>.000534 <span class="m">16</span> <span class="m">34</span> mmap
<span class="m">0</span>.43 <span class="m">0</span>.000013 <span class="m">1</span> <span class="m">18</span> getdents
<span class="m">12</span>.62 <span class="m">0</span>.000379 <span class="m">24</span> <span class="m">16</span> mprotect
<span class="m">2</span>.43 <span class="m">0</span>.000073 <span class="m">6</span> <span class="m">12</span> brk
<span class="m">1</span>.66 <span class="m">0</span>.000050 <span class="m">4</span> <span class="m">12</span> <span class="m">2</span> ioctl
<span class="m">5</span>.03 <span class="m">0</span>.000151 <span class="m">17</span> <span class="m">9</span> <span class="m">9</span> access
<span class="m">0</span>.37 <span class="m">0</span>.000011 <span class="m">1</span> <span class="m">8</span> lstat
<span class="m">2</span>.46 <span class="m">0</span>.000074 <span class="m">19</span> <span class="m">4</span> munmap
<span class="m">0</span>.30 <span class="m">0</span>.000009 <span class="m">3</span> <span class="m">3</span> dup
<span class="m">0</span>.27 <span class="m">0</span>.000008 <span class="m">3</span> <span class="m">3</span> <span class="m">1</span> readlink
<span class="m">0</span>.23 <span class="m">0</span>.000007 <span class="m">2</span> <span class="m">3</span> sigaltstack
<span class="m">0</span>.63 <span class="m">0</span>.000019 <span class="m">19</span> <span class="m">1</span> write
<span class="m">0</span>.53 <span class="m">0</span>.000016 <span class="m">16</span> <span class="m">1</span> rt_sigprocmask
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> getpid
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> execve
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> fcntl
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> sysinfo
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> getuid
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> getgid
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> geteuid
<span class="m">0</span>.00 <span class="m">0</span>.000000 <span class="m">0</span> <span class="m">1</span> getegid
<span class="m">0</span>.43 <span class="m">0</span>.000013 <span class="m">13</span> <span class="m">1</span> arch_prctl
<span class="m">0</span>.80 <span class="m">0</span>.000024 <span class="m">24</span> <span class="m">1</span> futex
<span class="m">0</span>.60 <span class="m">0</span>.000018 <span class="m">18</span> <span class="m">1</span> set_tid_address
<span class="m">0</span>.63 <span class="m">0</span>.000019 <span class="m">19</span> <span class="m">1</span> set_robust_list
<span class="m">0</span>.53 <span class="m">0</span>.000016 <span class="m">16</span> <span class="m">1</span> prlimit64
<span class="m">0</span>.73 <span class="m">0</span>.000022 <span class="m">22</span> <span class="m">1</span> getrandom
------ ----------- ----------- --------- --------- ----------------
<span class="m">100</span>.00 <span class="m">0</span>.003004 <span class="m">703</span> <span class="m">52</span> total
</code></pre></div>
<p>Do output quá dài nên ở đây thay đổi câu lệnh, để có đầy đủ output hãy chạy
với option C hoa:</p>
<div class="highlight"><pre><span></span><code>$ strace -CScalls python -c <span class="s1">'print("hello world")'</span>
</code></pre></div>
<p>Chương trình Python đơn giản này thực hiện tới 703 syscall, 52 fail.</p>
<h3>Kết luận</h3>
<p>27 với 703 thì cái nào "hơn"?</p>
<p>27 nhỏ hơn 703, còn 703 thì lớn hơn 27. Lập trình C cũng rất đơn giản, đúng
không!</p>
<p>Vậy nên khi lập trình, luôn nhớ rằng mọi thứ đều là tương đôi, đều phải đánh
đổi. Mấy em gái đã xinh mà code Python lại giỏi, chỉ có 2 khả năng xảy ra:</p>
<ul>
<li>1 là thiếu cái gì đó</li>
<li>2 là học Python ở <a href="https://pymi.vn">PYMI</a> rõ ràng <3.</li>
</ul>
<h3>Đọc thêm</h3>
<ul>
<li>System call: https://hvnsweeting.github.io/syscall.html</li>
</ul>Crawl dữ liệu bằng JavaScript ngay trên trình duyệt2020-02-09T00:00:00+07:002020-02-09T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2020-02-09:/article/jsdom/<p>trở thành hacker ngay trên trình duyệt - hay tại sao không nên tin vào ảnh chụp màn hình.</p><p>Năm 2020, bên cạnh cuộc chiến khốc liệt với virus Novel Corona (nCov), vẫn dai dẳng và mãnh liệt cuộc chiến chống tin giả, tin quá sốc, tràn lan trên các mạng xã hội.</p>
<p>Trong các loại tin giả, có không ít tin được đưa theo hình thức chụp ảnh màn hình điện thoại, chụp trình duyệt trên máy tính để làm bằng chứng. Ảnh màn hình điện thoại dễ dàng làm giả với một lập trình viên mobile, còn với ảnh trình duyệt, ai cũng làm được, không cần tới trình độ PhotoShop đỉnh cao hay kỹ thuật lập trình chuyên nghiệp cỡ như các Pymiers.</p>
<p>Bài viết sẽ vạch trần kỹ thuật làm giả này, đồng thời lợi dụng nó để crawl dữ liệu ngay trên trang web, dùng chính trình duyệt mà không cần thêm gì khác như BeautifulSoup4 hay <a href="/article/requests-bs4-requests-html/">requests-html</a>.</p>
<h2>Web page là gì</h2>
<p>Web page là trang web, sử dụng HTML để biểu diễn cấu trúc.</p>
<h2>HTML là gì</h2>
<p>HTML là một ngôn ngữ markup (đánh dấu), dùng để mô tả cấu trúc một web page. Nó sử dụng các tag (thẻ) để đánh dấu.
Chuột phải vào màn hình, chọn "View page source" để xem code HTML của web page.</p>
<p>HTML không phải một ngôn ngữ lập trình (programming language).</p>
<h3>HTML tag</h3>
<p>Mỗi bộ phận của web page được đánh dấu sử dụng một cặp thẻ tương ứng, <code><a ...> </a></code> để đánh dấu một web link, <code><table> ... </table></code> để đánh dấu một bảng ...
Xem đầy đủ các tag tại <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element">đây</a></p>
<h2>Document Object Model (DOM)</h2>
<p>Document Object Model (DOM) biểu diễn HTML ở dạng cây (tree structure).
DOM là programming interface cho HTML (và XML) documents.</p>
<p><a href="https://en.wikipedia.org/wiki/Document_Object_Model"><img alt="DOM wiki" src="http://pp.pymi.vn/images/220px-DOM-model.svg.png"></a></p>
<p>Sử dụng cây này có thể dễ dàng truy cập (query) các thành phần của web page, thay đổi chúng (manipulate).</p>
<p>Mỗi web page là một document.</p>
<h3>Truy cập & thay đổi DOM trên trình duyệt</h3>
<p>Mở một web site ra, rồi bật web console lên, hoặc đọc bài <a href="https://www.familug.org/2017/04/hoc-javascript-1.html">này</a> nếu chưa biết cách.</p>
<h3>Document, Window & DOM Elements</h3>
<p>Mỗi web page là một <code>document</code>, mỗi "cửa sổ" (thời nay là 1 tab trình duyệt), là một <code>window</code>, đại diện cho cửa sổ đó. Mỗi document là một tree, với các node gọi là các Element.</p>
<p>Gõ vào web console <code>document</code>, đây chính là DOM của web page đang truy cập, thông qua object này, ta có thể làm đủ trò với những gì đang hiện trên trình duyệt.</p>
<h3>Get element</h3>
<p>Có vài cách để truy cập element, trong đó kể tới 2 methods:</p>
<ul>
<li><code>document.getElementsByTagName</code>: lấy các elements theo HTML tag của chúng</li>
<li><code>document.querySelectorAll</code>: lấy các elements theo <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors">CSS selectors</a>.</li>
</ul>
<p><code>innerText</code> là thuộc tính (attribute) của một element, chứa nội dung text của nó. Ví dụ sau sẽ thay đổi tiêu đề của bài viết, đây là cách làm tin giả rồi chụp mành hình, không cần dùng PhotoShop:</p>
<p>Vào web page: <a href="https://www.familug.org/2020/02/vagrant.html">https://www.familug.org/2020/02/vagrant.html</a></p>
<p><img alt="Trước" src="http://pp.pymi.vn/images/vagrant.png"></p>
<p>Sau khi "hack"</p>
<div class="highlight"><pre><span></span><code><span class="nx">titleTag</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">'h3'</span><span class="p">)[</span><span class="mf">0</span><span class="p">]</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">titleTag</span><span class="p">.</span><span class="nx">innerText</span><span class="p">)</span>
<span class="nx">titleTag</span><span class="p">.</span><span class="nx">innerText</span> <span class="o">=</span> <span class="s2">"Happy birthday!"</span>
</code></pre></div>
<p><img alt="Hack FAMILUG" src="http://pp.pymi.vn/images/happybirthday.png"></p>
<h3>Query element</h3>
<p>Thay vì dùng HTML tags, có thể dùng CSS class để tìm ra các element, ví dụ sau đây tìm các tin tuyển dụng trên trang <a href="https://news.ycombinator.com/item?id=22225314">HackerNews</a> có chứa từ khóa Python và Remote, rồi hiển thị lên màn hình - có thể xem như crawl dữ liệu ngay trên trình duyệt:</p>
<div class="highlight"><pre><span></span><code><span class="nx">allcomms</span> <span class="o">=</span> <span class="nb">Array</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nb">document</span><span class="p">.</span><span class="nx">querySelectorAll</span><span class="p">(</span><span class="s2">".comment-tree .comtr"</span><span class="p">))</span>
<span class="nx">topcoms</span> <span class="o">=</span> <span class="nx">allcomms</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">e</span> <span class="p">=></span> <span class="nx">e</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="s1">'img[width="0"]'</span><span class="p">))</span>
<span class="nx">pys</span> <span class="o">=</span> <span class="nx">topcoms</span><span class="p">.</span><span class="nx">filter</span><span class="p">(</span><span class="nx">e</span> <span class="p">=></span> <span class="p">(</span><span class="sr">/\bpython\b/i</span><span class="p">).</span><span class="nx">test</span><span class="p">(</span><span class="nx">e</span><span class="p">.</span><span class="nx">innerText</span><span class="p">))</span>
<span class="nx">pyremotes</span> <span class="o">=</span> <span class="nx">pys</span><span class="p">.</span><span class="nx">map</span><span class="p">(</span><span class="nx">i</span> <span class="p">=></span> <span class="nx">i</span><span class="p">.</span><span class="nx">innerText</span><span class="p">).</span><span class="nx">filter</span><span class="p">(</span><span class="nx">e</span> <span class="p">=></span> <span class="p">(</span><span class="sr">/\bremote\b/i</span><span class="p">).</span><span class="nx">test</span><span class="p">(</span><span class="nx">e</span><span class="p">))</span>
<span class="nx">alert</span><span class="p">(</span><span class="nx">pyremotes</span><span class="p">.</span><span class="nx">join</span><span class="p">(</span><span class="s2">"\n"</span><span class="p">))</span>
</code></pre></div>
<p>Đoạn code trên học theo <a href="https://news.ycombinator.com/item?id=22231675">một comment trên HackerNews</a></p>
<p>Đọc thêm về map/filter tại <a href="https://gist.github.com/hvnsweeting/e26b6367bb0144d4ce3eaa04cfe3b94d">đây</a>.</p>
<p><img alt="remote python job" src="http://pp.pymi.vn/images/hackernews_py_remote_jobs.png"></p>
<p><code>/\bpython\b/i</code> là Regex Object, dùng để tìm các đoạn text chứa từ khóa Python, chữ <code>\b</code> để đánh dấu boundary, tức kết quả sẽ không chứa <code>pikapython</code> mà chỉ chứa <code>pika python</code>, <code>i</code> để không phân biệt chữ hoa chữ thường.</p>
<h2>Kết luận</h2>
<p>DOM là khái niệm cơ bản về web page, là nền tảng để lập trình JavaScript ở phía client (trên trình duyệt), dù không phải Front-end developer, cũng nên biết dùng DOM, để automate, để "hack".</p>
<p>Happy new year 2020!</p>Sử dụng xlwings tương tác với MS Excel2019-12-08T00:00:00+07:002018-12-08T00:00:00+07:00htlcnntag:pp.pymi.vn,2019-12-08:/article/xlwings-named-range/<p>Giới thiệu thư viện xlwings dùng để tương tác với MS Excel</p><p><a href="https://github.com/xlwings/xlwings">xlwings</a> là 1 thư viện Python dùng để tương tác giữa Python và MS Excel. Hoạt động được trên Windows và Mac. xlwings có các tính năng:</p>
<ul>
<li><strong>Scripting</strong>: Tự động hóa/tương tác với Excel từ môi trường Python, sử dụng cú pháp gần với VBA mà vẫn "Pythonic".</li>
<li><strong>Macros</strong>: Viết các script python thay thế cho VBA macros, giúp code dễ đọc hơn. Sau khi viết script python xong, chỉ cần gọi 1 hàm trong VBA là script chạy.</li>
<li><strong>UDFs</strong>: Viết hàm người dùng tự định nghĩa bằng ngôn ngữ Python và sử dụng được hàm đó trong excel (Windows only).</li>
<li><strong>REST API</strong>: cung cấp REST API cho Excel workbook.</li>
</ul>
<h2>Cài đặt:</h2>
<div class="highlight"><pre><span></span><code><span class="n">pip</span> <span class="n">install</span> <span class="n">xlwings</span>
</code></pre></div>
<h2>Sử dụng:</h2>
<ol>
<li>Một số thao tác cơ bản:</li>
</ol>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">xlwings</span> <span class="k">as</span> <span class="nn">xw</span>
<span class="c1"># mở workbook mới</span>
<span class="n">new_wb</span> <span class="o">=</span> <span class="n">xw</span><span class="o">.</span><span class="n">Book</span><span class="p">()</span>
<span class="c1"># hoặc</span>
<span class="n">opening_wb</span> <span class="o">=</span> <span class="n">xw</span><span class="o">.</span><span class="n">Book</span><span class="p">(</span><span class="s1">'name_of_opening_workbook'</span><span class="p">)</span>
<span class="c1"># hoặc</span>
<span class="n">open_new_workbook</span> <span class="o">=</span> <span class="n">xw</span><span class="o">.</span><span class="n">Book</span><span class="p">(</span><span class="s1">'full/relaive_path_to_xls'</span><span class="p">)</span>
<span class="c1"># Lấy các sheets có trong file excel </span>
<span class="n">xw</span><span class="o">.</span><span class="n">sheets</span>
<span class="c1"># Làm việc với dữ liệu trong 1 sheet</span>
<span class="n">sht</span> <span class="o">=</span> <span class="n">xw</span><span class="o">.</span><span class="n">sheets</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="nb">print</span><span class="p">(</span><span class="n">sht</span><span class="o">.</span><span class="n">range</span><span class="p">(</span><span class="s1">'A1'</span><span class="p">)</span><span class="o">.</span><span class="n">value</span><span class="p">)</span>
<span class="n">sht</span><span class="o">.</span><span class="n">range</span><span class="p">(</span><span class="s1">'A1'</span><span class="p">)</span><span class="o">.</span><span class="n">value</span> <span class="o">=</span> <span class="s1">'xxx'</span>
<span class="c1"># tự động tìm bảng</span>
<span class="c1"># https://docs.xlwings.org/en/stable/datastructures.html#range-expanding</span>
<span class="n">sht</span><span class="o">.</span><span class="n">range</span><span class="p">(</span><span class="s1">'A1'</span><span class="p">)</span><span class="o">.</span><span class="n">expand</span><span class="p">()</span>
<span class="c1"># ...</span>
</code></pre></div>
<p><strong>Ngoài ra, <a href="https://docs.xlwings.org/en/stable/datastructures.html#pandas-dataframes">xlwings còn tích hợp pandas</a>.</strong></p>
<ol>
<li>Xử lý trường hợp cụ thể:
Trong video <a href="https://www.youtube.com/watch?v=wm7RNejVh8E">này</a>, xlwings được dùng để tự động tạo các named range cho hàng loạt file có cấu trúc tương tự. Mục đích là để làm bước tiếp theo: link giá trị từ nhiều file vào 1 file tổng hợp, dựa vào named range trong từng file chi tiết. Tham khảo <a href="https://gist.github.com/htlcnn/0ddd4e0023e0b623bc0e6006a9f9520c">script</a>.</li>
</ol>
<p>Trong script này còn sử dụng Excel VBA API: <a href="https://docs.microsoft.com/en-us/office/vba/api/excel.names.add">WorkBook.Names.Add</a> (tra cứu toàn bộ ở <a href="https://docs.microsoft.com/en-us/office/vba/api/overview/excel/object-model">đây</a>, được cung cấp qua <code>xw_object.api</code>). Tức là những gì không được cung cấp sẵn cú pháp Pythonic, ta sẽ sử dụng VBA API.</p>
<p>Hạn chế: range expand chỉ ứng dụng được cho vùng dữ liệu liên tục. Với "bảng" có các dòng/cột trống (để "format" cho đẹp) thì sẽ bị ngắt tại chỗ có dòng/cột trống. Vì vậy, thường sẽ phải chọn 1 range khá lớn/1 magic number để lấy được toàn bộ vùng dữ liệu cần xử lý.</p>
<p>Mọi việc phải xử lý trong video demo sẽ đơn giản hơn nếu có 1 file excel mẫu ban đầu, được định nghĩa sẵn các named range, sau đó mới duplicate ra để điền dữ liệu vào. Tuy nhiên, thực tế không như thế, ta phải xử lý lỗi một cách nhanh nhất, cũng giống như khi tập chép phạt: <code>"Anh xin lỗi\n" * 100</code></p>Biết Python - quen ngay Julia2019-04-11T00:00:00+07:002019-04-11T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2019-04-11:/article/julia_intro/<p>Giống Python, mà nhanh gấp 2++, cũng rất gì và này nọ.</p><p>Julia là ngôn ngữ lập trình mới, trông rất giống code Python, chạy nhanh hơn Python3 <a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/faster/julia-python3.html">ít nhất 2 lần</a>
và có nhiều tính năng hấp dẫn. Bạn không cần phải từ bỏ Python hay ngôn ngữ XYZ để học Julia, Julia chỉ đơn giản là một bông hoa đẹp mà ai thích... thì múc.</p>
<p><img alt="Julia" src="https://docs.julialang.org/en/v1/assets/logo.png"></p>
<p>Julia là ngôn ngữ lập trình "làm gì cũng được", nhưng được tập trung vào lĩnh vực tính toán khoa học và muốn
thế chỗ Python (nhưng đường còn dài, và còn nhiều hơn chông gai).</p>
<h3>Cài đặt</h3>
<p>Xem https://docs.julialang.org/en/v1/</p>
<p>Tại thời điểm viết bài (2019 tháng 4), Julia ở bản 1.1.0 - đã ổn định hơn trước rất nhiều, ít bug hơn, chạy nhanh hơn.</p>
<h3>Từ Python ngó sang</h3>
<p>Julia trông rất giống Python, cách dùng các học, cách code cũng tương tự, nên nếu biết Python thì
việc viết được code để làm công việc thường ngày (của 1 DevOps) chỉ trong vòng vài tiếng.</p>
<h4>REPL</h4>
<p>Gõ lệnh trực tiếp bằng lệnh <code>julia</code> trên Linux/MacOS</p>
<div class="highlight"><pre><span></span><code><span class="o">$</span> <span class="n">julia</span>
<span class="n">_</span>
<span class="n">_</span> <span class="n">_</span> <span class="n">_</span><span class="p">(</span><span class="n">_</span><span class="p">)</span><span class="n">_</span> <span class="o">|</span> <span class="n">Documentation</span><span class="o">:</span> <span class="n">https</span><span class="o">://</span><span class="n">docs</span><span class="o">.</span><span class="n">julialang</span><span class="o">.</span><span class="n">org</span>
<span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="o">|</span> <span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="o">|</span>
<span class="n">_</span> <span class="n">_</span> <span class="n">_</span><span class="o">|</span> <span class="o">|</span><span class="n">_</span> <span class="n">__</span> <span class="n">_</span> <span class="o">|</span> <span class="kt">Type</span> <span class="s">"?"</span> <span class="k">for</span> <span class="n">help</span><span class="p">,</span> <span class="s">"]?"</span> <span class="k">for</span> <span class="n">Pkg</span> <span class="n">help</span><span class="o">.</span>
<span class="o">|</span> <span class="o">|</span> <span class="o">|</span> <span class="o">|</span> <span class="o">|</span> <span class="o">|</span> <span class="o">|/</span> <span class="sa">_</span><span class="sb">` | |</span>
<span class="sb"> | | |_| | | | (_| | | Version 1.1.0 (2019-01-21)</span>
<span class="sb"> _/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release</span>
<span class="sb">|__/ |</span>
<span class="sb">julia> 1 + 1</span>
<span class="sb">2</span>
</code></pre></div>
<p>Đây gọi là chế độ "REPL" (Read-Eval-Print-Loop) == (gõ vào - chạy - in ra kết quả - cứ thế mà làm).</p>
<h4>IPython</h4>
<p>Không cần cài thêm gì, Julia REPL tự có đủ các tính năng thiết yếu của IPython.</p>
<p>Đọc document của 1 function? Gõ ? rồi gõ bất cứ cái gì cần hỏi:</p>
<div class="highlight"><pre><span></span><code><span class="n">julia</span><span class="o">></span> <span class="mi">1</span> <span class="o">+</span> <span class="mi">1</span>
<span class="mi">2</span>
<span class="c">###### gõ ? xong , biến từ julia> thành help?></span>
<span class="n">help</span><span class="o">?></span> <span class="n">sum</span>
<span class="n">search</span><span class="o">:</span> <span class="n">sum</span> <span class="n">sum!</span> <span class="n">summary</span> <span class="n">cumsum</span> <span class="n">cumsum!</span> <span class="n">isnumeric</span> <span class="kt">VersionNumber</span> <span class="n">issubnormal</span> <span class="n">get_zero_subnormals</span>
<span class="o">...</span>
<span class="n">sum</span><span class="p">(</span><span class="n">itr</span><span class="p">)</span>
<span class="n">Returns</span> <span class="n">the</span> <span class="n">sum</span> <span class="n">of</span> <span class="n">all</span> <span class="n">elements</span> <span class="k">in</span> <span class="n">a</span> <span class="n">collection</span><span class="o">.</span>
</code></pre></div>
<p>Màu mè (syntax highlight) cũng tự có sẵn rồi.</p>
<h4>Chạy file code</h4>
<p>File code Julia có đuôi <code>.jl</code>, ví dụ <code>hello.jl</code>.
Chạy file code bằng cách gõ lệnh: <code>julia hello.jl</code></p>
<h4>Cài đặt package</h4>
<p>Để cài các package trên mạng, sử dụng package manager CÓ SẴN của Julia.
Chuyện về <code>virtualenv</code> tạm chưa bàn tới ở đây vì khá rõ ràng là không cần thiết (cài package
<a href="http://pymi.vn/blog/virtualenv/">không cần gõ sudo gì cả</a>).</p>
<p>Việc cài đặt package trong Julia thực hiện hơi khác so với Python Pip hay NodeJS NPM một chút.
Sẽ không có câu lệnh nào để gõ cả, không có chương trình thêm nào hết.
Julia thực hiện cài đặt package khi một đoạn code Julia được chạy (gọi function thực hiện cài đặt).</p>
<p>Ví dụ một file tên (tuỳ ý) như sau:</p>
<div class="highlight"><pre><span></span><code><span class="c"># gicungduoc.jl</span>
<span class="k">using</span> <span class="n">Pkg</span>
<span class="n">Pkg</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s">"HTTP"</span><span class="p">)</span>
<span class="n">Pkg</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s">"JSON"</span><span class="p">)</span>
<span class="n">Pkg</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="s">"DocOpt"</span><span class="p">)</span>
</code></pre></div>
<p>Chạy code này: <code>julia gicungduoc.jl</code> sẽ cài đặt các package, sau đó cứ thế mà dùng các thư viện này.</p>
<h3>The good, the OK, the ugly</h3>
<h4>The good - phần tốt</h4>
<h5>Pipe operator</h5>
<p>Tốt hay xấu là tuỳ do từng người tự phán xét.
Hãy xem đoạn code sau:</p>
<p>Python: <code>''.join(" Py thon ".strip().split())</code></p>
<p>Julia: <code>" Py thon " |> strip |> split |> xs -> join(xs, "")</code></p>
<p>Python</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">double</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
<span class="k">def</span> <span class="nf">incr</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="k">return</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="nb">print</span><span class="p">(</span><span class="n">incr</span><span class="p">(</span><span class="n">double</span><span class="p">(</span><span class="n">incr</span><span class="p">(</span><span class="mi">2</span><span class="p">))))</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="n">double</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">=</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
<span class="n">incr</span><span class="p">(</span><span class="n">x</span><span class="p">)</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="n">println</span><span class="p">(</span><span class="mi">2</span> <span class="o">|></span> <span class="n">incr</span> <span class="o">|></span> <span class="n">double</span> <span class="o">|></span> <span class="n">incr</span><span class="p">)</span>
<span class="c"># 7</span>
</code></pre></div>
<p><code>|></code> gọi là <code>Piping operator</code>, lấy đầu ra của function này làm đầu vào cho function khác.
Giúp việc goi funciton liên tục (và đọc nó) dễ dàng hơn.
Giống UNIX command.</p>
<h5>Nhanh</h5>
<p>Julia thường nhanh hơn Python <a href="https://benchmarksgame-team.pages.debian.net/benchmarksgame/faster/julia-python3.html">ít nhất 2 lần</a></p>
<h5>Không quan trọng indentation</h5>
<p>Dù bạn thụt ra thụt vào 2 3 4 5 ký tự, hay dùng tab đều không quan trọng.
Chuyện này vốn gây đau đầu cho không ít người dùng Python.</p>
<p>Python</p>
<div class="highlight"><pre><span></span><code><span class="n">s</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1000</span><span class="p">):</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">s</span> <span class="o">=</span> <span class="n">s</span> <span class="o">+</span> <span class="n">i</span>
<span class="nb">print</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
</code></pre></div>
<p>Julia</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">:</span><span class="mi">999</span>
<span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span>
<span class="k">global</span> <span class="n">s</span> <span class="o">=</span> <span class="n">s</span> <span class="o">+</span> <span class="n">i</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">println</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="c"># 233168</span>
</code></pre></div>
<p>Julia dùng từ <code>end</code> để kết thúc <code>if</code> hay <code>for</code>, nên không cần thiết sử dụng dấu cách hay tab để thụt dòng.
Thậm chí có thể dùng <code>;</code> để phân cách các phần, và viết trên 1 dòng (bạn KHÔNG THỂ làm điều này với Python khi có for):</p>
<div class="highlight"><pre><span></span><code><span class="n">julia</span><span class="o">></span> <span class="n">s</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="k">for</span> <span class="n">i</span> <span class="k">in</span> <span class="mi">1</span><span class="o">:</span><span class="mi">999</span><span class="p">;</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="o">||</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">;</span> <span class="k">global</span> <span class="n">s</span> <span class="o">=</span> <span class="n">s</span> <span class="o">+</span> <span class="n">i</span> <span class="p">;</span> <span class="k">end</span> <span class="p">;</span> <span class="k">end</span><span class="p">;</span> <span class="n">println</span><span class="p">(</span><span class="n">s</span><span class="p">)</span>
<span class="mi">233168</span>
</code></pre></div>
<h5>Range bao gồm cả số kết thúc</h5>
<p>Một điều gây khó chịu với người mới code Python là phần kết thúc của range không được tính.
Tức nếu viết <code>range(1,1000)</code> thì chỉ có từ 1 đến 999. Trong đầu luôn phải nhớ bớt đi 1.
Julia <code>1:999</code> nghĩa là 1 đến 999, không thêm bớt gì.</p>
<h5>Không bị "leak" biến i trong vòng lặp ra ngoài</h5>
<p>Đây là <a href="https://stackoverflow.com/questions/3611760/scoping-in-python-for-loops">1 bug của Python</a>, do sau này quá muộn để sửa, <a href="https://www.wired.com/story/its-not-a-bug-its-a-feature/">người ta coi nó như 1 tính năng</a>.</p>
<div class="highlight"><pre><span></span><code><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">]:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">i</span><span class="p">)</span> <span class="c1"># i vốn ở trong vòng lặp for, nay thoát ra ngoài với giá trị = 3</span>
</code></pre></div>
<h5>Hỗ trợ functional</h5>
<p>Viết map trong Julia rất dễ chịu - dễ đọc:</p>
<div class="highlight"><pre><span></span><code><span class="nv">map</span><span class="ss">(</span>[<span class="mi">1</span>,<span class="mi">2</span>,<span class="mi">3</span>]<span class="ss">)</span> <span class="k">do</span> <span class="nv">x</span>
<span class="nv">x</span> <span class="o">+</span> <span class="mi">1</span>
<span class="k">end</span>
</code></pre></div>
<p>Cách viết dùng lambda: </p>
<div class="highlight"><pre><span></span><code><span class="n">map</span><span class="p">(</span><span class="n">x</span> <span class="o">-></span> <span class="n">x</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">])</span>
</code></pre></div>
<p>sẽ tạo array chứa <code>[2,3,4]</code></p>
<p>Python</p>
<div class="highlight"><pre><span></span><code><span class="nb">list</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">]))</span>
</code></pre></div>
<h4>The OK - ổn</h4>
<h5>Index from 1</h5>
<p>Chuyện này gây sốc với lập trình viên lâu năm C, Java, PHP, Python ...
Nhưng không phải là hiếm có. Lua, MatLab, R đều dùng index từ 1.</p>
<div class="highlight"><pre><span></span><code><span class="n">ns</span><span class="o">=</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">];</span> <span class="n">print</span><span class="p">(</span><span class="n">ns</span><span class="p">[</span><span class="mi">4</span><span class="p">])</span>
</code></pre></div>
<p>Trong Julia code này sẽ in ra số 4 nhưng Python sẽ xảy ra exception do Python index đếm từ 0</p>
<p>Dùng nhiều sẽ quen và cũng không ghê gớm lắm, do Julia sử dụng cả phần cuối của range.</p>
<p>Ví dụ slice: Python lấy 3 số đầu tiên của list:</p>
<div class="highlight"><pre><span></span><code><span class="n">ns</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">3</span><span class="p">]</span>
</code></pre></div>
<p>Julia: </p>
<div class="highlight"><pre><span></span><code><span class="n">ns</span><span class="p">[</span><span class="mi">1</span><span class="o">:</span><span class="mi">3</span><span class="p">]</span>
</code></pre></div>
<p>Đều kết thúc là 3 - đều là số phần tử cần lấy.</p>
<p>Nhưng tệ hơn khi cần lấy 2 phần tử cuối:</p>
<div class="highlight"><pre><span></span><code><span class="n">ns</span><span class="p">[</span><span class="k">end</span><span class="o">-</span><span class="mi">1</span><span class="o">:</span><span class="k">end</span><span class="p">]</span>
</code></pre></div>
<p><code>end</code> là một index magic (tự xuất hiện), đại diện cho index của phần tử cuối cùng.</p>
<p>Trong ví dụ này gõ số 4 thay end cũng được.</p>
<h4>The ugly - thảm hại</h4>
<p>Unicode string - sẽ rất đau đầu khi chuyển từ Python sang.</p>
<p>Index của string (mặc định Unicode) trong Julia là byte index, không phải index theo thứ tự của ký tự.</p>
<p>Python</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">16</span><span class="p">]:</span> <span class="n">w</span> <span class="o">=</span> <span class="s2">"Điêu"</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">20</span><span class="p">]:</span> <span class="n">w</span><span class="p">[</span><span class="mi">0</span><span class="p">],</span><span class="n">w</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">20</span><span class="p">]:</span> <span class="p">(</span><span class="s1">'Đ'</span><span class="p">,</span> <span class="s1">'i'</span><span class="p">)</span>
</code></pre></div>
<p>Julia </p>
<div class="highlight"><pre><span></span><code><span class="n">ulia</span><span class="o">></span> <span class="n">w</span> <span class="o">=</span> <span class="s">"Điêu"</span>
<span class="s">"Điêu"</span>
<span class="n">julia</span><span class="o">></span> <span class="n">w</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="sc">'Đ'</span><span class="o">:</span> <span class="n">Unicode</span> <span class="n">U</span><span class="o">+</span><span class="mi">0110</span> <span class="p">(</span><span class="n">category</span> <span class="n">Lu</span><span class="o">:</span> <span class="n">Letter</span><span class="p">,</span> <span class="n">uppercase</span><span class="p">)</span>
<span class="n">julia</span><span class="o">></span> <span class="n">w</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="n">ERROR</span><span class="o">:</span> <span class="kt">StringIndexError</span><span class="p">(</span><span class="s">"Điêu"</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span>
<span class="n">Stacktrace</span><span class="o">:</span>
<span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="n">string_index_err</span><span class="p">(</span><span class="o">::</span><span class="kt">String</span><span class="p">,</span> <span class="o">::</span><span class="kt">Int64</span><span class="p">)</span> <span class="n">at</span> <span class="o">/</span><span class="n">nix</span><span class="o">/</span><span class="n">store</span><span class="o">/</span><span class="mi">2</span><span class="n">fmf5sqi0jx5zdlqx0gpw2m6nrsbcch2</span><span class="o">-</span><span class="n">julia</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">julia</span><span class="o">/</span><span class="n">sys</span><span class="o">.</span><span class="n">dylib</span><span class="o">:?</span>
<span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="n">getindex_continued</span><span class="p">(</span><span class="o">::</span><span class="kt">String</span><span class="p">,</span> <span class="o">::</span><span class="kt">Int64</span><span class="p">,</span> <span class="o">::</span><span class="kt">UInt32</span><span class="p">)</span> <span class="n">at</span> <span class="o">/</span><span class="n">nix</span><span class="o">/</span><span class="n">store</span><span class="o">/</span><span class="mi">2</span><span class="n">fmf5sqi0jx5zdlqx0gpw2m6nrsbcch2</span><span class="o">-</span><span class="n">julia</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">julia</span><span class="o">/</span><span class="n">sys</span><span class="o">.</span><span class="n">dylib</span><span class="o">:?</span>
<span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="n">getindex</span><span class="p">(</span><span class="o">::</span><span class="kt">String</span><span class="p">,</span> <span class="o">::</span><span class="kt">Int64</span><span class="p">)</span> <span class="n">at</span> <span class="o">/</span><span class="n">nix</span><span class="o">/</span><span class="n">store</span><span class="o">/</span><span class="mi">2</span><span class="n">fmf5sqi0jx5zdlqx0gpw2m6nrsbcch2</span><span class="o">-</span><span class="n">julia</span><span class="o">-</span><span class="mf">1.0.1</span><span class="o">/</span><span class="n">lib</span><span class="o">/</span><span class="n">julia</span><span class="o">/</span><span class="n">sys</span><span class="o">.</span><span class="n">dylib</span><span class="o">:?</span>
<span class="p">[</span><span class="mi">4</span><span class="p">]</span> <span class="n">top</span><span class="o">-</span><span class="n">level</span> <span class="n">scope</span> <span class="n">at</span> <span class="n">none</span><span class="o">:</span><span class="mi">0</span>
<span class="n">julia</span><span class="o">></span> <span class="n">w</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span>
<span class="sc">'i'</span><span class="o">:</span> <span class="n">ASCII</span><span class="o">/</span><span class="n">Unicode</span> <span class="n">U</span><span class="o">+</span><span class="mi">0069</span> <span class="p">(</span><span class="n">category</span> <span class="n">Ll</span><span class="o">:</span> <span class="n">Letter</span><span class="p">,</span> <span class="n">lowercase</span><span class="p">)</span>
</code></pre></div>
<p>Do chữ <code>Đ</code> trong Unicode UTF-8 được biểu diễn bằng 2 byte, ta chỉ có thể truy cập
được index 1, không truy cập được index 2. Và chữ <code>i</code> ngay sau <code>Đ</code> sẽ là index 3.</p>
<h4>Exception</h4>
<p>TBD</p>
<h4>Chú ý</h4>
<ul>
<li>Julia khởi động mất 0.2-0.4 giây, Python khởi động nhanh gấp 10.</li>
<li>String trong Julia phải dùng double quote <code>""</code>, single quote <code>''</code> dành cho ký tự (Char).</li>
<li>Nối string dùng <code>*</code> chứ không phải <code>+</code>.</li>
<li>Sẽ không có method gắn liền vào các object như string hay list trong Python,
thay vào đó là các function có sẵn (thường không cần import, gọi là trong <a href="https://docs.julialang.org/en/v1/base/base/">Base</a>)</li>
</ul>
<h3>Ví dụ 1 chương trình CLI nhận argument, gọi HTTP với JSON</h3>
<div class="highlight"><pre><span></span><code><span class="k">import</span> <span class="n">HTTP</span>
<span class="k">import</span> <span class="n">JSON</span>
<span class="k">using</span> <span class="n">DocOpt</span> <span class="c"># import docopt function</span>
<span class="k">function</span> <span class="n">main</span><span class="p">()</span>
<span class="n">doc</span> <span class="o">=</span> <span class="s">"""Fist Julia program which makes HTTP requests to httpbin endpoint</span>
<span class="s"> Usage:</span>
<span class="s"> main.jl request <endpoint></span>
<span class="s"> Options:</span>
<span class="s"> -h --help Show this screen.</span>
<span class="s"> --version Show version.</span>
<span class="s"> """</span>
<span class="n">args</span> <span class="o">=</span> <span class="n">docopt</span><span class="p">(</span><span class="n">doc</span><span class="p">,</span> <span class="n">version</span><span class="o">=</span><span class="sa">v</span><span class="s">"0.0.1"</span><span class="p">)</span>
<span class="n">endpoint</span> <span class="o">=</span> <span class="n">args</span><span class="p">[</span><span class="s">"<endpoint>"</span><span class="p">]</span>
<span class="n">url</span> <span class="o">=</span> <span class="s">"https://httpbin.org/</span><span class="si">$endpoint</span><span class="s">"</span>
<span class="n">println</span><span class="p">(</span><span class="s">"Accessing </span><span class="si">$url</span><span class="s">"</span><span class="p">)</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">HTTP</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">JSON</span><span class="o">.</span><span class="n">Parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="kt">String</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">body</span><span class="p">))</span>
<span class="n">println</span><span class="p">(</span><span class="s">"My IP is "</span> <span class="o">*</span> <span class="n">d</span><span class="p">[</span><span class="s">"origin"</span><span class="p">])</span>
<span class="n">println</span><span class="p">(</span><span class="s">"Now also send post"</span><span class="p">)</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">HTTP</span><span class="o">.</span><span class="n">request</span><span class="p">(</span><span class="s">"POST"</span><span class="p">,</span> <span class="s">"https://httpbin.org/post"</span><span class="p">,</span>
<span class="p">[</span><span class="s">"Content-Type"</span> <span class="o">=></span> <span class="s">"application/json"</span><span class="p">],</span>
<span class="n">JSON</span><span class="o">.</span><span class="n">json</span><span class="p">(</span><span class="kt">Dict</span><span class="p">(</span><span class="sc">'a'</span><span class="o">=></span><span class="mi">2</span><span class="p">,</span> <span class="sc">'b'</span><span class="o">=></span><span class="mi">3</span><span class="p">))</span>
<span class="p">)</span>
<span class="n">data</span> <span class="o">=</span> <span class="n">JSON</span><span class="o">.</span><span class="n">Parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="kt">String</span><span class="p">(</span><span class="n">r</span><span class="o">.</span><span class="n">body</span><span class="p">))</span>
<span class="n">open</span><span class="p">(</span><span class="s">"/tmp/data.json"</span><span class="p">,</span> <span class="s">"w"</span><span class="p">)</span> <span class="k">do</span> <span class="n">f</span>
<span class="n">write</span><span class="p">(</span><span class="n">f</span><span class="p">,</span> <span class="n">JSON</span><span class="o">.</span><span class="n">json</span><span class="p">(</span><span class="n">data</span><span class="p">[</span><span class="s">"json"</span><span class="p">]))</span>
<span class="k">end</span>
<span class="n">open</span><span class="p">(</span><span class="s">"/tmp/data.json"</span><span class="p">,</span> <span class="s">"r"</span><span class="p">)</span> <span class="k">do</span> <span class="n">f</span>
<span class="n">d</span> <span class="o">=</span> <span class="n">JSON</span><span class="o">.</span><span class="n">Parser</span><span class="o">.</span><span class="n">parse</span><span class="p">(</span><span class="kt">String</span><span class="p">(</span><span class="n">read</span><span class="p">(</span><span class="n">f</span><span class="p">)))</span>
<span class="n">println</span><span class="p">(</span><span class="n">d</span><span class="p">)</span>
<span class="k">end</span>
<span class="n">open</span><span class="p">(</span><span class="sb">`ls -l`</span><span class="p">)</span> <span class="k">do</span> <span class="n">io</span>
<span class="k">for</span> <span class="n">line</span> <span class="k">in</span> <span class="n">eachline</span><span class="p">(</span><span class="n">io</span><span class="p">)</span>
<span class="k">if</span> <span class="o">!</span><span class="k">isa</span><span class="p">(</span><span class="n">match</span><span class="p">(</span><span class="sa">r</span><span class="sr">".*</span><span class="err">\</span><span class="sr">.jl"</span><span class="p">,</span> <span class="n">line</span><span class="p">),</span> <span class="kt">Nothing</span><span class="p">)</span>
<span class="n">println</span><span class="p">(</span><span class="n">line</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>Chạy</p>
<div class="highlight"><pre><span></span><code>$ julia main.jl -h
Fist Julia program which makes HTTP requests to httpbin endpoint
Usage:
main.jl request <endpoint>
Options:
-h --help Show this screen.
--version Show version.
$ julia main.jl request ip
Accessing https://httpbin.org/ip
My IP is <span class="m">3</span>.117.2.254, <span class="m">3</span>.117.2.254
Now also send post
Dict<span class="o">{</span>String,Any<span class="o">}(</span><span class="s2">"b"</span><span class="o">=</span>>3,<span class="s2">"a"</span><span class="o">=</span>>2<span class="o">)</span>
-rw-r--r-- <span class="m">1</span> viethung.nguyen viethung.nguyen <span class="m">1189</span> Apr <span class="m">12</span> <span class="m">08</span>:38 main.jl
</code></pre></div>
<p>Code hoàn toàn tương đương với code Python.</p>
<h3>Kết luận</h3>
<p>Còn chờ gì nữa?
Làm tí Julia thôi.</p>
<p>Phần tiếp theo (nếu có) sẽ đi vào các tính năng của Julia sử dụng trong tính toán khoa học.</p>
<h3>Tham khảo</h3>
<ul>
<li><a href="https://docs.julialang.org/en/v1/">https://docs.julialang.org/en/v1/</a></li>
<li><a href="https://en.wikibooks.org/wiki/Introducing_Julia">https://en.wikibooks.org/wiki/Introducing_Julia</a></li>
<li><a href="https://lectures.quantecon.org/jl/">https://lectures.quantecon.org/jl/</a></li>
</ul>Dùng MicroPython với wifi board ESP-82662018-10-21T00:00:00+07:002018-10-21T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-10-21:/article/micropython/<p>Chạy code Python trên thiết bị với chỉ 96KB RAM và 4MB bộ nhớ flash.</p><p>Khi trở thành một trong những ngôn ngữ lập trình phổ biến nhất thế giới,
Python không chỉ được dùng trong làm web, data science, hay sysadmin tool mà
nó còn đá sang cả một lĩnh vực vốn giới hạn về tài nguyên: nhúng.</p>
<p>Bình thường khi bật Python lên để in ra màn hình dòng "hello, world" ta đã
dùng tới 8MB bộ nhớ. Vậy nên việc dùng Python cho lập trình nhúng thường nghe
có vẻ không phù hợp.</p>
<p><a href="http://micropython.org">MicroPython</a> ra đời, là một bản thu gọn của Python, tối ưu chạy trên các vi xử
lý cũng như các môi trường giới hạn về tài nguyên.
Bài này khám phá việc chạy MicroPython trên một thiết bị tí hon
có tên WEMOS D1 mini.</p>
<h2>WEMOS D1 mini</h2>
<p>Là 1 bảng mạch điện có khả năng thu phát sóng wifi (wifi board) có kích thước
nhỏ hơn ngón chân cái của người lớn, thiết bị này có giá 2 USD.</p>
<p><img alt="WEMOS D1 Mini" src="http://pp.pymi.vn/images/wemos1.jpg"></p>
<p>Bảng mạch này sử dụng vi điều khiển (microcontroller) ESP-8266, với xung nhịp
80MHz/160MHz, 96KB RAM và 4 MB Flash.</p>
<h3>Microcontroller</h3>
<blockquote>
<p>A microcontroller is a small computer on a single integrated circuit. In modern
terminology, it is similar to, but less sophisticated than, a system on a chip
or SoC; an SoC may include a microcontroller as one of its components. A
microcontroller contains one or more CPUs along with memory and programmable
input/output peripherals.</p>
</blockquote>
<p>Một vi điều khiển là một máy tính nhỏ, trên một mạch điện tử (P/S: SoC là khái
niệm tương tự, nhưng phức tạp hơn). Nó có một hoặc nhiều CPU, memory và
chỗ để thực hiện vào ra dữ liệu.</p>
<p>Hãy nhìn vào chiếc máy bàn hay laptop bạn đang dùng, nó có 1 cục CPU to bằng
lòng bàn tay, vài thanh RAM dài như bàn tay, các cổng vào ra đều to bằng ngón
tay. Mỗi cục CPU đều từ 2-3 GHz (so với 80MHz), ổ cứng vài trăm GB (so với 4MB),
1-16 GB RAM (so với 96KB RAM).
Ta sẽ thấy microcontroller nhỏ bé và "yếu ớt" đến chừng nào.</p>
<p><img alt="WEMOS D1 Mini" src="http://pp.pymi.vn/images/esp8266.jpg"></p>
<h3>MicroPython</h3>
<p>Phiên bản Python dành riêng cho các thiết bị tí hon này nhỏ tới mức chỉ cần
256kB không gian chứa code, và 16kB RAM.</p>
<h2>Cài đặt MicroPython lên WEMOS D1 Mini trên Ubuntu 16.04</h2>
<p>(Windows hay OSX cần cài driver, hãy lên trang chủ của WEMOS để tải và xem
hướng dẫn).</p>
<p>Cắm thiết bị vào máy tính sử dụng dây micro USB (dây sạc điện thoại android),
sẽ thấy nháy đèn trên thiết bị, gõ lệnh <code>dmesg</code> để xem Linux kernel báo nhận
thiết bị:</p>
<div class="highlight"><pre><span></span><code>$ dmesg
...
<span class="o">[</span> <span class="m">992</span>.253009<span class="o">]</span> usb <span class="m">2</span>-1: new full-speed USB device number <span class="m">5</span> using xhci_hcd
<span class="o">[</span> <span class="m">992</span>.402181<span class="o">]</span> usb <span class="m">2</span>-1: New USB device found, <span class="nv">idVendor</span><span class="o">=</span>1a86, <span class="nv">idProduct</span><span class="o">=</span><span class="m">7523</span>
<span class="o">[</span> <span class="m">992</span>.402185<span class="o">]</span> usb <span class="m">2</span>-1: New USB device strings: <span class="nv">Mfr</span><span class="o">=</span><span class="m">0</span>, <span class="nv">Product</span><span class="o">=</span><span class="m">2</span>, <span class="nv">SerialNumber</span><span class="o">=</span><span class="m">0</span>
<span class="o">[</span> <span class="m">992</span>.402187<span class="o">]</span> usb <span class="m">2</span>-1: Product: USB2.0-Serial
<span class="o">[</span> <span class="m">992</span>.402757<span class="o">]</span> ch341 <span class="m">2</span>-1:1.0: ch341-uart converter detected
<span class="o">[</span> <span class="m">992</span>.403119<span class="o">]</span> usb <span class="m">2</span>-1: ch341-uart converter now attached to ttyUSB0
</code></pre></div>
<div class="highlight"><pre><span></span><code>$ file /dev/ttyUSB0
/dev/ttyUSB0: character special <span class="o">(</span><span class="m">188</span>/0<span class="o">)</span>
</code></pre></div>
<p>Ta có thấy xuất hiện 1 file mới gọi là <code>/dev/ttyUSB0</code>, đây chính là thiết bị
vừa kết nối.</p>
<h3>Cài đặt MicroPython sử dụng esptool</h3>
<p>Cài đặt esptool dùng pip:</p>
<div class="highlight"><pre><span></span><code>$ pip install esptool
Collecting esptool
...
Successfully installed ecdsa-0.13 esptool-2.5.1 pyaes-1.6.1 pyserial-3.4
</code></pre></div>
<p>Xóa nội dung hiện tại trong flash memory</p>
<div class="highlight"><pre><span></span><code><span class="o">$</span> <span class="n">sudo</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">hvn</span><span class="o">/</span><span class="n">py36</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">esptool</span><span class="o">.</span><span class="n">py</span> <span class="o">--</span><span class="n">port</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span> <span class="n">erase_flash</span>
<span class="n">esptool</span><span class="o">.</span><span class="n">py</span> <span class="n">v2</span><span class="o">.</span><span class="mf">5.1</span>
<span class="n">Serial</span> <span class="n">port</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
<span class="n">Connecting</span><span class="o">....</span>
<span class="n">Detecting</span> <span class="n">chip</span> <span class="n">type</span><span class="o">...</span> <span class="n">ESP8266</span>
<span class="n">Chip</span> <span class="k">is</span> <span class="n">ESP8266EX</span>
<span class="n">Features</span><span class="p">:</span> <span class="n">WiFi</span>
<span class="n">MAC</span><span class="p">:</span> <span class="n">b4</span><span class="p">:</span><span class="n">e6</span><span class="p">:</span><span class="mi">2</span><span class="n">d</span><span class="p">:</span><span class="mi">3</span><span class="n">b</span><span class="p">:</span><span class="mi">22</span><span class="p">:</span><span class="mi">1</span><span class="n">b</span>
<span class="n">Uploading</span> <span class="n">stub</span><span class="o">...</span>
<span class="n">Running</span> <span class="n">stub</span><span class="o">...</span>
<span class="n">Stub</span> <span class="n">running</span><span class="o">...</span>
<span class="n">Erasing</span> <span class="n">flash</span> <span class="p">(</span><span class="n">this</span> <span class="n">may</span> <span class="n">take</span> <span class="n">a</span> <span class="k">while</span><span class="p">)</span><span class="o">...</span>
<span class="n">Chip</span> <span class="n">erase</span> <span class="n">completed</span> <span class="n">successfully</span> <span class="ow">in</span> <span class="mf">1.6</span><span class="n">s</span>
<span class="n">Hard</span> <span class="n">resetting</span> <span class="n">via</span> <span class="n">RTS</span> <span class="n">pin</span><span class="o">...</span>
</code></pre></div>
<p>Tải MicroPython bản dành cho ESP8266: bản mới nhất tại thời điểm viết bài
http://micropython.org/resources/firmware/esp8266-20180511-v1.9.4.bin</p>
<p>Ghi micropython lên flash memory:</p>
<div class="highlight"><pre><span></span><code><span class="o">$</span> <span class="n">sudo</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">hvn</span><span class="o">/</span><span class="n">py36</span><span class="o">/</span><span class="n">bin</span><span class="o">/</span><span class="n">esptool</span><span class="o">.</span><span class="n">py</span> <span class="o">--</span><span class="n">port</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span> <span class="o">--</span><span class="n">baud</span> <span class="mi">460800</span> <span class="n">write_flash</span> <span class="o">--</span><span class="n">flash_size</span><span class="o">=</span><span class="n">detect</span> <span class="o">-</span><span class="n">fm</span> <span class="n">dio</span> <span class="mi">0</span> <span class="o">~/</span><span class="n">esp8266</span><span class="o">-</span><span class="mi">20180511</span><span class="o">-</span><span class="n">v1</span><span class="o">.</span><span class="mf">9.4</span><span class="o">.</span><span class="n">bin</span>
<span class="n">esptool</span><span class="o">.</span><span class="n">py</span> <span class="n">v2</span><span class="o">.</span><span class="mf">5.1</span>
<span class="n">Serial</span> <span class="n">port</span> <span class="o">/</span><span class="n">dev</span><span class="o">/</span><span class="n">ttyUSB0</span>
<span class="n">Connecting</span><span class="o">....</span>
<span class="n">Detecting</span> <span class="n">chip</span> <span class="n">type</span><span class="o">...</span> <span class="n">ESP8266</span>
<span class="n">Chip</span> <span class="k">is</span> <span class="n">ESP8266EX</span>
<span class="n">Features</span><span class="p">:</span> <span class="n">WiFi</span>
<span class="n">MAC</span><span class="p">:</span> <span class="n">b4</span><span class="p">:</span><span class="n">e6</span><span class="p">:</span><span class="mi">2</span><span class="n">d</span><span class="p">:</span><span class="mi">3</span><span class="n">b</span><span class="p">:</span><span class="mi">22</span><span class="p">:</span><span class="mi">1</span><span class="n">b</span>
<span class="n">Uploading</span> <span class="n">stub</span><span class="o">...</span>
<span class="n">Running</span> <span class="n">stub</span><span class="o">...</span>
<span class="n">Stub</span> <span class="n">running</span><span class="o">...</span>
<span class="n">Changing</span> <span class="n">baud</span> <span class="n">rate</span> <span class="n">to</span> <span class="mi">460800</span>
<span class="n">Changed</span><span class="o">.</span>
<span class="n">Configuring</span> <span class="n">flash</span> <span class="n">size</span><span class="o">...</span>
<span class="n">Auto</span><span class="o">-</span><span class="n">detected</span> <span class="n">Flash</span> <span class="n">size</span><span class="p">:</span> <span class="mi">4</span><span class="n">MB</span>
<span class="n">Flash</span> <span class="n">params</span> <span class="n">set</span> <span class="n">to</span> <span class="mh">0x0240</span>
<span class="n">Compressed</span> <span class="mi">604872</span> <span class="n">bytes</span> <span class="n">to</span> <span class="mf">394893.</span><span class="o">..</span>
<span class="n">Wrote</span> <span class="mi">604872</span> <span class="n">bytes</span> <span class="p">(</span><span class="mi">394893</span> <span class="n">compressed</span><span class="p">)</span> <span class="n">at</span> <span class="mh">0x00000000</span> <span class="ow">in</span> <span class="mf">9.0</span> <span class="n">seconds</span> <span class="p">(</span><span class="n">effective</span> <span class="mf">536.4</span> <span class="n">kbit</span><span class="o">/</span><span class="n">s</span><span class="p">)</span><span class="o">...</span>
<span class="n">Hash</span> <span class="n">of</span> <span class="n">data</span> <span class="n">verified</span><span class="o">.</span>
<span class="n">Leaving</span><span class="o">...</span>
<span class="n">Hard</span> <span class="n">resetting</span> <span class="n">via</span> <span class="n">RTS</span> <span class="n">pin</span><span class="o">...</span>
</code></pre></div>
<p>Sau khi cài đặt xong, bấm nút "reset" trên thiết bị để khởi động lại, ngay sau
đó bật WIFI của laptop hay điện thoại lên ta sẽ thấy một mạng WIFI có tên
<code>MicroPython-xxxxxx</code>, có thể kết nối vào WIFI này với password <code>micropythoN</code>.
Ở đây ta không cần làm việc này. Tiếp tục cài đặt để kết nối với thiết bị qua
cổng COM:</p>
<p>Cài <code>picocom</code>:</p>
<div class="highlight"><pre><span></span><code>$ sudo apt install -y picocom
$ sudo picocom /dev/ttyUSB0 --baud <span class="m">115200</span>
picocom v1.7
port is : /dev/ttyUSB0
flowcontrol : none
baudrate is : <span class="m">115200</span>
parity is : none
databits are : <span class="m">8</span>
escape is : C-a
<span class="nb">local</span> <span class="nb">echo</span> is : no
noinit is : no
noreset is : no
nolock is : no
send_cmd is : sz -vv
receive_cmd is : rz -vv
imap is :
omap is :
emap is : crcrlf,delbs,
Terminal ready
>>> <span class="m">2</span>**1000
<span class="m">10715086071862673209484250490600018105614048117055336074437503883703510511249361224931983788156958581275946729175531468251871452856923140435984577574698574803934567774824230985421074605062371141877954182153046474983581941267398767559165543946077062914571196477686542167660429831652624386837205668069376</span>
</code></pre></div>
<p>Sau khi kết nối, ta lập tức sử dụng Python interpreter như bình thường.</p>
<p>Python interpreter đóng vai trò như 1 hệ điều hành ở đây, mỗi lần reset thiết bị,
nó sẽ lại bật lại Python interpreter.
Mọi tương tác với thiết bị (đọc/ghi file) đều thực hiện qua code python.</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">import</span> <span class="nn">os</span>
<span class="o">>>></span> <span class="n">os</span><span class="o">.</span><span class="n">getcwd</span><span class="p">()</span>
<span class="s1">'/'</span>
<span class="o">>>></span> <span class="n">os</span><span class="o">.</span><span class="n">listdir</span><span class="p">()</span>
<span class="p">[</span><span class="s1">'boot.py'</span><span class="p">,</span> <span class="s1">'main.py'</span><span class="p">]</span>
</code></pre></div>
<p>Hai file này được chạy mỗi lần thiết bị khởi động.</p>
<p>Xem nội dung file <code>boot.py</code>:</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'boot.py'</span><span class="p">)</span>
<span class="o">>>></span> <span class="nb">print</span><span class="p">(</span><span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">())</span>
<span class="c1"># This file is executed on every boot (including wake-boot from deepsleep)</span>
<span class="c1">#import esp</span>
<span class="c1">#esp.osdebug(None)</span>
<span class="kn">import</span> <span class="nn">gc</span>
<span class="c1">#import webrepl</span>
<span class="c1">#webrepl.start()</span>
<span class="n">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">()</span>
</code></pre></div>
<p>File <code>main.py</code> chứa code của người dùng muốn chạy mỗi lần bật thiết bị. Ví dụ ta muốn hiển thị dòng hello PyMi:</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="n">f</span> <span class="o">=</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'main.py'</span><span class="p">,</span> <span class="s1">'wt'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">f</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="s1">'print("Hello Pymi.vn")'</span><span class="p">)</span>
<span class="mi">22</span>
<span class="o">>>></span> <span class="n">f</span><span class="o">.</span><span class="n">close</span><span class="p">()</span>
</code></pre></div>
<p>Bấm nút reset sẽ thấy:</p>
<div class="highlight"><pre><span></span><code>...
<span class="nv">ets_task</span><span class="ss">(</span><span class="mi">40100130</span>, <span class="mi">3</span>, <span class="mi">3</span><span class="nv">fff83ec</span>, <span class="mi">4</span><span class="ss">)</span>
<span class="nv">Hello</span> <span class="nv">Pymi</span>.<span class="nv">vn</span>
<span class="nv">MicroPython</span> <span class="nv">v1</span>.<span class="mi">9</span>.<span class="mi">4</span><span class="o">-</span><span class="mi">8</span><span class="o">-</span><span class="nv">ga9a3caad0</span> <span class="nv">on</span> <span class="mi">2018</span><span class="o">-</span><span class="mi">05</span><span class="o">-</span><span class="mi">11</span><span class="c1">; ESP module with ESP8266</span>
<span class="nv">Type</span> <span class="s2">"</span><span class="s">help()</span><span class="s2">"</span> <span class="k">for</span> <span class="nv">more</span> <span class="nv">information</span>.
<span class="o">>>></span>
</code></pre></div>
<p>Thao tác với các chân Pin</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">from</span> <span class="nn">machine</span> <span class="kn">import</span> <span class="n">Pin</span>
<span class="o">>>></span> <span class="n">P0</span> <span class="o">=</span> <span class="n">Pin</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">Pin</span><span class="o">.</span><span class="n">OUT</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">P0</span><span class="o">.</span>
<span class="vm">__class__</span> <span class="n">IN</span> <span class="n">IRQ_FALLING</span> <span class="n">IRQ_RISING</span>
<span class="n">OPEN_DRAIN</span> <span class="n">OUT</span> <span class="n">PULL_UP</span> <span class="n">init</span>
<span class="n">irq</span> <span class="n">off</span> <span class="n">on</span> <span class="n">value</span>
<span class="o">>>></span> <span class="n">P0</span><span class="o">.</span><span class="n">on</span><span class="p">()</span>
<span class="o">>>></span> <span class="n">P0</span><span class="o">.</span><span class="n">off</span><span class="p">()</span>
<span class="o">>>></span>
</code></pre></div>
<h3>Baud</h3>
<p>Baud /ˈbɔːd/ ở câu lệnh trên là một độ đo "symbol rate" (symbol per second), để
quyết định tốc độ giao tiếp qua một kênh thông tin.</p>
<h2>Kết luận</h2>
<p>Với MicroPython trên thiết bị, ta đã có thể thực hiện điều khiển các chân Pin hay gửi HTTP request như code Python bình thường. Giờ thì giới hạn chỉ còn là trí tưởng tượng của bạn.</p>
<h2>Tham khảo</h2>
<ul>
<li>http://docs.micropython.org/en/latest/esp8266/tutorial/intro.html#intro</li>
<li>https://wiki.wemos.cc/products:d1:d1_mini</li>
</ul>Dùng Raspberry Pi để gửi và nhận SMS với Sim900A2018-10-09T00:00:00+07:002018-10-09T00:00:00+07:00tudoanhtag:pp.pymi.vn,2018-10-09:/article/raspberry-pi-sim900a/<p>Summary: Hướng dẫn kết nối module Sim900A với Raspberry Pi 3 B+, dùng để gửi và nhận tin nhắn SMS</p>
<p>Dạo này vì lý do công việc nên cần phải tìm cách kết nối và sử dụng module Sim900A với con Raspberry Pi 3, dùng để nhận SMS.</p>
<p>Tìm hiểu …</p><p>Summary: Hướng dẫn kết nối module Sim900A với Raspberry Pi 3 B+, dùng để gửi và nhận tin nhắn SMS</p>
<p>Dạo này vì lý do công việc nên cần phải tìm cách kết nối và sử dụng module Sim900A với con Raspberry Pi 3, dùng để nhận SMS.</p>
<p>Tìm hiểu mấy hôm, rất là cực nên hôm nay mình viết lại bài này, cho những ai cần tới sau này đỡ tốn thời gian mày mò lại từ đầu.</p>
<h3>Chuẩn bị</h3>
<p>Để bắt đầu, bạn cần:</p>
<ul>
<li>
<p>Một combo <strong>Raspberry Pi 3 B+</strong> đầy đủ nguồn, thẻ nhớ, đã cài đặt <strong>Raspbian</strong></p>
</li>
<li>
<p>Một module <strong>Sim900A</strong></p>
</li>
</ul>
<p><img alt="https://i.imgur.com/vvIy62p.jpg" src="https://i.imgur.com/vvIy62p.jpg"></p>
<ul>
<li>Dây chuyển đổi USB - UART PL2303 (Có thể không có)</li>
</ul>
<p><img alt="https://i.imgur.com/wWkr422.jpg" src="https://i.imgur.com/wWkr422.jpg"></p>
<ul>
<li>Đầy đủ dây nối</li>
</ul>
<p><img alt="https://i.imgur.com/C40X3gs.png" src="https://i.imgur.com/C40X3gs.png"></p>
<h3>Kết nối</h3>
<p>Chỉ cần bạn kết nối đúng các cổng <em>Tx, Rx, Gnd và nguồn 5v</em> vào Pi là xong. Tham khảo ảnh dưới:</p>
<p><img alt="https://i.ytimg.com/vi/louSyBRkvO4/maxresdefault.jpg" src="https://i.ytimg.com/vi/louSyBRkvO4/maxresdefault.jpg"></p>
<p>Ảnh thực tế (Nếu kết nối thẳng vào GPIO)</p>
<p><img alt="https://i.imgur.com/KRanp3y.jpg" src="https://i.imgur.com/KRanp3y.jpg"></p>
<p><img alt="https://i.imgur.com/dQhoN8e.jpg" src="https://i.imgur.com/dQhoN8e.jpg"></p>
<p>Còn nếu kết nối qua USB</p>
<p><img alt="https://i.imgur.com/2DHsQCx.jpg" src="https://i.imgur.com/2DHsQCx.jpg"></p>
<h3>Cài đặt</h3>
<p>Đầu tiên ta cần mở cổng <strong>ttyS0</strong></p>
<p>Bạn có thể dùng "raspi-config" để mở UART:</p>
<p><img alt="https://i.imgur.com/NVchkKl.png" src="https://i.imgur.com/NVchkKl.png"></p>
<ol>
<li>Trong "Interfacing Options", chọn "Serial"</li>
<li>Chọn "No" khi được hỏi "You want a login shell over serial?".</li>
<li>Chọn "Yes" khi được hỏi "You want the hardware enabled?"</li>
<li>Khởi động lại</li>
</ol>
<p>Thực ra, bạn có thể sửa file <em>/boot/config.txt</em> và sửa <strong>enable_uart=1</strong>, và <strong>console=serial0,115200</strong> (hoặc <strong>console=ttyS0,115200</strong>) ở trong <em>/boot/cmdline.txt</em></p>
<p>Và bạn có thể bật <em>terminal</em> lên và test xem kết nối thành công chưa bằng cách gửi command <strong>AT\r\n</strong> đến khi nào trả về <strong>OK</strong> là thành công. (Nếu kết nối qua GPIO thì sẽ là <strong>/dev/ttyS0</strong>, còn qua USB thì sẽ là <strong>/dev/ttyUSB0</strong>)</p>
<p>Ví dụ:</p>
<p><img alt="https://i.imgur.com/59K45tV.png" src="https://i.imgur.com/59K45tV.png"></p>
<p>Link asciinema:</p>
<p><a href="https://asciinema.org/a/U3oPOg0vX4CZ5u3mWTTIr0FTS"><img alt="asciicast" src="https://asciinema.org/a/U3oPOg0vX4CZ5u3mWTTIr0FTS.png"></a></p>
<p>Sau đó bạn có thể gửi và đọc tin nhắn bằng lib mình đã viết ở đây (Ví dụ cách sử dụng ở trong link):</p>
<p>https://github.com/tudoanh/sim900a</p>
<p>Ví dụ về kết quả nhận được bằng lệnh <strong>AT+CNMI=3,2,0,0,0</strong></p>
<p><img alt="https://i.imgur.com/ecz65hk.png" src="https://i.imgur.com/ecz65hk.png"></p>
<p>Về chi tiết cách sử dụng các lệnh AT và các khái niệm GPIO, UART mình sẽ để sang một bài khác.</p>
<p>Chúc các bạn thành công.</p>
<p>Tham khảo:</p>
<ol>
<li>https://pinout.xyz/</li>
<li>http://mlab.vn/9216-huong-dan-lap-trinh-module-sim900a-va-arduino.html</li>
<li>https://www.developershome.com/sms/checkCommandSupport3.asp</li>
<li>https://raspberrypi.stackexchange.com/questions/82696/how-do-i-connect-gsm-sim-900a-to-a-raspberry-pi-3</li>
<li>https://www.espruino.com/datasheets/SIM900_AT.pdf</li>
<li>https://hristoborisov.com/index.php/projects/turning-the-raspberry-pi-into-a-sms-center-using-python/#Connecting_the_3gModem</li>
</ol>Quản lý, sử dụng package trong CHICKEN Scheme2018-10-05T00:00:00+07:002018-10-05T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-10-05:/article/scm_egg/<p>Con đường nhanh nhất để sử dụng 1 ngôn ngữ là dùng nó!</p><p>Học và hành thường ở rất xa nhau.
Ta có thể học đủ loại khái niệm, đủ loại cú pháp, viết các thuật toán để luyện
ngôn ngữ, nhưng rồi lại mãi luẩn quẩn ở đó, không thoát ra được, không bay
lên nổi. Viết 1 vòng for, 2 câu if bằng Python, JavaScript, Golang hay
<a href="https://elixir.pymi.vn/">Elixir</a>
cũng không hơn nhau gì cả, chỉ thay đổi chút cú pháp thôi.</p>
<p>Thế nên cách "tốt nhất" để học 1 ngôn ngữ lập trình, không phải là học đủ các
khái niệm, best practice, viết code chuẩn đẹp tuyệt đối, mà là việc hoàn thành
một sản phẩm từ đầu đến tận X Y Z. Đưa ra sản phẩm cuối cùng, có thể là trang
web chạy với domain hẳn hoi, có thể là chương trình đóng thành package,
thành file cài đặt, hay game có giao diện đầy đủ.</p>
<p>Package manager giúp cài đặt các thư viện có sẵn là công cụ giúp ta đến đích
nhanh nhất. Python có pip, JavaScript có NPM, Elixir có mix, Rust có cargo ...
thì CHICKEN Scheme có <code>chicken-install</code>, câu lệnh có sẵn khi cài CHICKEN
Scheme.</p>
<h2>Cài đặt extension</h2>
<p>CHICKEN Scheme gọi các gói thư viện là extension hay "egg".</p>
<p><code>chicken-install tên-extension</code></p>
<p>Ví dụ cài <code>regex</code>:</p>
<div class="highlight"><pre><span></span><code>$ sudo chicken-install regex
<span class="o">[</span>sudo<span class="o">]</span> password <span class="k">for</span> hvn: GO PASSWORD
retrieving ...
connecting to host <span class="s2">"chicken.kitten-technologies.co.uk"</span>, port <span class="m">80</span> ...
requesting <span class="s2">"/henrietta.cgi?name=regex&mode=default"</span> ...
reading response ...
HTTP/1.1 <span class="m">200</span> OK
Date: Fri, <span class="m">05</span> Oct <span class="m">2018</span> <span class="m">14</span>:28:32 GMT
Server: Apache/2.2.31 <span class="o">(</span>Unix<span class="o">)</span> DAV/2 PHP/5.5.36 mod_fastcgi/2.4.6
...
checking platform <span class="k">for</span> <span class="sb">`</span>regex<span class="s1">' ...</span>
<span class="s1">checking dependencies for `regex'</span> ...
install order:
<span class="o">(</span><span class="s2">"regex"</span><span class="o">)</span>
installing regex:1.0 ...
changing current directory to /tmp/tempab50.21917/regex
<span class="s1">'/usr/bin/csi'</span> -bnq -setup-mode -e <span class="s2">"(require-library setup-api)"</span> -e <span class="s2">"(import setup-api)"</span> -e <span class="s2">"(setup-error-handling)"</span> -e <span class="s2">"(extension-name-and-version '(\"regex\" \"1.0\"))"</span> <span class="s1">'regex.setup'</span>
<span class="s1">'/usr/bin/csc'</span> -feature compiling-extension -setup-mode -s -O3 -d1 regex.scm -JS
<span class="s1">'/usr/bin/csc'</span> -feature compiling-extension -setup-mode -s -O3 -d0 regex.import.scm
cp -r <span class="s1">'regex.so'</span> <span class="s1">'/var/lib/chicken/7/regex.so'</span>
chmod a+r <span class="s1">'/var/lib/chicken/7/regex.so'</span>
cp -r <span class="s1">'regex.import.so'</span> <span class="s1">'/var/lib/chicken/7/regex.import.so'</span>
chmod a+r <span class="s1">'/var/lib/chicken/7/regex.import.so'</span>
chmod a+r <span class="s1">'/var/lib/chicken/7/regex.setup-info'</span>
</code></pre></div>
<p>Bật <code>csi</code> lên để gõ code gọi function <code>grep</code>:</p>
<div class="highlight"><pre><span></span><code><span class="nv">$</span> <span class="nv">csi</span>
<span class="nv">/usr/bin/rlwrap</span>
<span class="nv">CHICKEN</span>
<span class="p">(</span><span class="nf">c</span><span class="p">)</span> <span class="mi">2008-2014</span><span class="o">,</span> <span class="nv">The</span> <span class="nv">Chicken</span> <span class="nv">Team</span>
<span class="p">(</span><span class="nf">c</span><span class="p">)</span> <span class="mi">2000-2007</span><span class="o">,</span> <span class="nv">Felix</span> <span class="nv">L</span><span class="o">.</span> <span class="nv">Winkelmann</span>
<span class="nv">Version</span> <span class="mf">4.9</span><span class="o">.</span><span class="mf">0.1</span> <span class="p">(</span><span class="nf">stability/4</span><span class="o">.</span><span class="mf">9.0</span><span class="p">)</span> <span class="p">(</span><span class="nf">rev</span> <span class="mi">8</span><span class="nv">b3189b</span><span class="p">)</span>
<span class="nv">linux-unix-gnu-x86-64</span> <span class="p">[</span> <span class="mi">64</span><span class="nv">bit</span> <span class="nv">manyargs</span> <span class="nv">dload</span> <span class="nv">ptables</span> <span class="p">]</span>
<span class="nv">bootstrapped</span> <span class="mi">2014-06-07</span>
<span class="o">#</span><span class="c1">;1> (grep "[0-9]" (list "pymi" "2018" "pymi2018"))</span>
<span class="nv">Error:</span> <span class="nv">unbound</span> <span class="nv">variable:</span> <span class="nv">grep</span>
<span class="nv">Call</span> <span class="nv">history:</span>
<span class="nv"><syntax></span> <span class="p">(</span><span class="nf">grep</span> <span class="s">"[0-9]"</span> <span class="p">(</span><span class="nb">list </span><span class="s">"pymi"</span> <span class="s">"2018"</span> <span class="s">"pymi2018"</span><span class="p">))</span>
<span class="nv"><syntax></span> <span class="p">(</span><span class="nb">list </span><span class="s">"pymi"</span> <span class="s">"2018"</span> <span class="s">"pymi2018"</span><span class="p">)</span>
<span class="nv"><eval></span> <span class="p">(</span><span class="nf">grep</span> <span class="s">"[0-9]"</span> <span class="p">(</span><span class="nb">list </span><span class="s">"pymi"</span> <span class="s">"2018"</span> <span class="s">"pymi2018"</span><span class="p">))</span> <span class="nv"><--</span>
</code></pre></div>
<p>Nội dung error rõ ràng: <code>Error: unbound variable: grep</code> - không hiểu <code>grep</code>
là cái gì - nó không phải một variable/function đã tồn tại.</p>
<h2>Sử dụng / import extension</h2>
<p>Dùng macro <code>use ten-extension</code> (tại bài này cứ hiểu là function cũng tạm ổn)</p>
<div class="highlight"><pre><span></span><code><span class="o">#</span><span class="c1">;1> (use regex)</span>
<span class="c1">; loading /var/lib//chicken/7/regex.import.so ...</span>
<span class="c1">; loading /var/lib//chicken/7/chicken.import.so ...</span>
<span class="c1">; loading /var/lib//chicken/7/irregex.import.so ...</span>
<span class="c1">; loading /var/lib//chicken/7/regex.so ...</span>
<span class="o">#</span><span class="c1">;2> (grep "[0-9]" (list "pymi" "2018" "pymi2018"))</span>
<span class="p">(</span><span class="s">"2018"</span> <span class="s">"pymi2018"</span><span class="p">)</span>
</code></pre></div>
<p>Ta thu được list các phần tử có chứa số, string "[0-9]" là regular expression
biểu diễn một số hệ 10.</p>
<h2>Đọc tài liệu của module/function</h2>
<p><code>grep</code> ở trên làm gì? CHICKEN Scheme có chương trình <code>chicken-doc</code> để tra
tài liêu, tương tự python có <code>pydoc</code>. Để có <code>chicken-doc</code>, cần cài extension
với lệnh: <code>sudo chicken-install chicken-doc</code>.</p>
<div class="highlight"><pre><span></span><code>$ chicken-doc grep
path: <span class="o">(</span>regex grep<span class="o">)</span>
-- procedure: <span class="o">(</span>grep REGEX LIST <span class="o">[</span>ACCESSOR<span class="o">])</span>
Returns all items of <span class="sb">`</span>LIST<span class="sb">`</span> that match the regular expression <span class="sb">`</span>REGEX<span class="sb">`</span>. This procedure could be defined as follows:
<span class="o">(</span>define <span class="o">(</span>grep regex lst<span class="o">)</span>
<span class="o">(</span>filter <span class="o">(</span>lambda <span class="o">(</span>x<span class="o">)</span> <span class="o">(</span>string-search regex x<span class="o">))</span> lst<span class="o">)</span> <span class="o">)</span>
<span class="sb">`</span>ACCESSOR<span class="sb">`</span> is an optional accessor-procedure applied to each element before doing the match. It should take a single argument
and <span class="k">return</span> a string that will <span class="k">then</span> be used <span class="k">in</span> the regular expression matching. <span class="sb">`</span>ACCESSOR<span class="sb">`</span> defaults to the identity <span class="k">function</span>.
</code></pre></div>
<div class="highlight"><pre><span></span><code>$ chicken-doc -i scheme <span class="c1"># đầy đủ doc về R5RS</span>
...
$ chicken-doc -c scheme <span class="c1"># danh sách các procedure trong R5RS</span>
...
$ chicken-doc data-structures
path: <span class="o">(</span>data-structures<span class="o">)</span>
<span class="o">==</span> Unit data-structures
This unit contains a collection of procedures related to data structures. This unit is used by default, unless the
program is compiled with the <span class="sb">`</span>-explicit-use<span class="sb">`</span> option.
...
$ chicken-doc when <span class="c1"># tìm từ khóa when, thấy 2 kết quả</span>
Found <span class="m">2</span> matches:
<span class="o">(</span>spock when<span class="o">)</span> <span class="o">(</span>when X1 BODY ...<span class="o">)</span>
<span class="o">(</span>chicken special-forms when<span class="o">)</span> <span class="o">(</span>when TEST EXP1 EXP2 ...<span class="o">)</span>
$ chicken-doc chicken special-forms when <span class="c1"># xem kết quả thứ 2</span>
-- syntax: <span class="o">(</span>when TEST EXP1 EXP2 ...<span class="o">)</span>
Equivalent to:
<span class="o">(</span><span class="k">if</span> TEST <span class="o">(</span>begin EXP1 EXP2 ...<span class="o">))</span>
</code></pre></div>
<p>Pydoc đi kèm với Python có khả năng tương tự:</p>
<div class="highlight"><pre><span></span><code>$ pydoc len
Help on built-in <span class="k">function</span> len <span class="k">in</span> module builtins:
len<span class="o">(</span>obj, /<span class="o">)</span>
Return the number of items <span class="k">in</span> a container.
</code></pre></div>
<h2>Liệt kê các extension có trên "mạng"</h2>
<div class="highlight"><pre><span></span><code>$ chicken-install -list
2d-primitives
3viewer
9ML-toolkit
9p
AD
F-operator
R
abnf
accents-substitute
...
</code></pre></div>
<p>Tại thời điểm viết bài, có 781 egg trong kho - hơi ít.</p>
<h2>Xem các extension library đã cài</h2>
<p>Dùng lệnh <code>chicken-status</code></p>
<div class="highlight"><pre><span></span><code>$ chicken-status
apropos ......................................... version: <span class="m">2</span>.1.0
check-errors .................................... version: <span class="m">2</span>.2.0
chicken-doc ..................................... version: <span class="m">0</span>.4.7
chicken-doc-cmd ................................. version: <span class="m">0</span>.4.7
fmt ............................................. version: <span class="m">0</span>.808
fmt-c ........................................... version: <span class="m">0</span>.808
fmt-color ....................................... version: <span class="m">0</span>.808
fmt-js .......................................... version: <span class="m">0</span>.808
fmt-unicode ..................................... version: <span class="m">0</span>.808
iset .............................................. version: <span class="m">2</span>.0
</code></pre></div>
<h2>Gỡ extension</h2>
<p>Sử dụng câu lệnh <code>chicken-uninstall TEN-extension</code>.</p>
<h2>Danh sách với thông tin các extension</h2>
<p>Xem online tai <a href="">http://eggs.call-cc.org/4/</a> cho bản CHICKEN stable hiện tại:
version 4.</p>
<p>Bài viết thực hiện trên:</p>
<div class="highlight"><pre><span></span><code><span class="o">$</span> <span class="n">lsb_release</span> <span class="o">-</span><span class="n">r</span><span class="p">;</span> <span class="n">csi</span> <span class="o">-</span><span class="n">version</span>
<span class="n">Release</span><span class="p">:</span> <span class="mf">16.04</span>
<span class="n">CHICKEN</span>
<span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="mi">2008</span><span class="o">-</span><span class="mi">2014</span><span class="p">,</span> <span class="n">The</span> <span class="n">Chicken</span> <span class="n">Team</span>
<span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="mi">2000</span><span class="o">-</span><span class="mi">2007</span><span class="p">,</span> <span class="n">Felix</span> <span class="n">L</span><span class="o">.</span> <span class="n">Winkelmann</span>
<span class="n">Version</span> <span class="mf">4.9</span><span class="o">.</span><span class="mf">0.1</span> <span class="p">(</span><span class="n">stability</span><span class="o">/</span><span class="mf">4.9</span><span class="o">.</span><span class="mi">0</span><span class="p">)</span> <span class="p">(</span><span class="n">rev</span> <span class="mi">8</span><span class="n">b3189b</span><span class="p">)</span>
<span class="n">linux</span><span class="o">-</span><span class="n">unix</span><span class="o">-</span><span class="n">gnu</span><span class="o">-</span><span class="n">x86</span><span class="o">-</span><span class="mi">64</span> <span class="p">[</span> <span class="mi">64</span><span class="n">bit</span> <span class="n">manyargs</span> <span class="n">dload</span> <span class="n">ptables</span> <span class="p">]</span>
<span class="n">bootstrapped</span> <span class="mi">2014</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">07</span>
</code></pre></div>
<p>Hết phần 2.</p>Tuple comprehension2018-09-10T00:00:00+07:002018-09-10T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-09-10:/article/tuple_comps/<p>List comprehension, set/dict comprehension, còn tuple? Generator là gì?</p><p>Python có sẵn (builtin) 4 kiểu dữ liệu "compound"/"container" quan trọng,
chúng dùng để chứa các giá trị. <code>list</code>, <code>tuple</code>, <code>set</code>, <code>dict</code>.</p>
<p>Một lập trình viên Python phải nắm thành thạo cả 4 kiểu dữ liệu trên và vận
dụng linh hoạt, biết điểm mạnh yếu của từng kiểu. Bàn về chuyện <em>ấy</em> lại cần
vài bài dài dòng khác nên sẽ giữ lại "để sau"/"lúc nào rảnh".</p>
<h2>Comprehension</h2>
<p><strong>Comprehension</strong> là một đặc sản của Python, một khi biết đến là hay bị nghiện,
thứ cú pháp ngắn gọn sạch sẽ ngọt như mía lùi cũng chính là vũ khí
giết những con ong ham ăn.</p>
<p>Cú pháp này Python <a href="https://docs.python.org/3/whatsnew/2.0.html">"vay mượn" mà không bao giờ trả từ
Haskell</a>
- một ngôn ngữ
vốn có tiếng khó học và vi diệu. Hãy thử xem 2 ví dụ sau:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="n">names</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'vuong'</span><span class="p">,</span> <span class="s1">'tron'</span><span class="p">,</span> <span class="s1">'tam giac'</span><span class="p">]</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="n">more_than_four_chars</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">3</span><span class="p">]:</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">names</span><span class="p">:</span>
<span class="o">...</span><span class="p">:</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="o">></span> <span class="mi">4</span><span class="p">:</span>
<span class="o">...</span><span class="p">:</span> <span class="n">more_than_four_chars</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">name</span><span class="o">.</span><span class="n">title</span><span class="p">())</span>
<span class="o">...</span><span class="p">:</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">4</span><span class="p">]:</span> <span class="nb">print</span><span class="p">(</span><span class="n">more_than_four_chars</span><span class="p">)</span>
<span class="p">[</span><span class="s1">'Vuong'</span><span class="p">,</span> <span class="s1">'Tam Giac'</span><span class="p">]</span>
</code></pre></div>
<p>4 dòng code (dòng print không tính)
trên để ta filter (lọc) từ một tập hợp đã cho, thu về các phần tử
thỏa mãn điều kiện (hơn 4 ký tự) rồi map (biến đổi) các ký tự đứng đầu thành
chữ hoa.</p>
<p>Dù đẹp sạch dễ hiểu lắm rồi, nhưng so với list comprehension:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">5</span><span class="p">]:</span> <span class="n">names</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'vuong'</span><span class="p">,</span> <span class="s1">'tron'</span><span class="p">,</span> <span class="s1">'tam giac'</span><span class="p">]</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">6</span><span class="p">]:</span> <span class="nb">print</span><span class="p">([</span><span class="n">name</span><span class="o">.</span><span class="n">title</span><span class="p">()</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">names</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">name</span><span class="p">)</span> <span class="o">></span> <span class="mi">4</span><span class="p">])</span>
<span class="p">[</span><span class="s1">'Vuong'</span><span class="p">,</span> <span class="s1">'Tam Giac'</span><span class="p">]</span>
</code></pre></div>
<p>thì sự ngắn gọn, đơn giản này đánh bại hoàn toàn cú pháp thông thường.</p>
<p>Và ta còn làm tương tự được với <code>set</code>, <code>dict</code>:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">9</span><span class="p">]:</span> <span class="p">{</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">]</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">}</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">9</span><span class="p">]:</span> <span class="p">{</span><span class="mi">2</span><span class="p">,</span> <span class="mi">4</span><span class="p">}</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">10</span><span class="p">]:</span> <span class="p">{</span><span class="n">i</span><span class="p">:</span> <span class="n">i</span><span class="o">**</span><span class="mi">2</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="mi">10</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">}</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">10</span><span class="p">]:</span> <span class="p">{</span><span class="mi">5</span><span class="p">:</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">7</span><span class="p">:</span> <span class="mi">49</span><span class="p">,</span> <span class="mi">9</span><span class="p">:</span> <span class="mi">81</span><span class="p">}</span>
</code></pre></div>
<h2>Tuple comprehension</h2>
<p>Dễ tưởng tượng ra là ta sẽ có luôn <strong>tuple comprehension</strong> - vì nghe có vẻ hợp
lý mà:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">13</span><span class="p">]:</span> <span class="nb">type</span><span class="p">((</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span><span class="mi">10</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">))</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">13</span><span class="p">]:</span> <span class="o"><</span><span class="k">class</span> <span class="err">'</span><span class="nc">generator</span><span class="s1">'></span>
</code></pre></div>
<p>SAI! khái niệm <strong>tuple comprehension</strong> không tồn tại. Cùng cú pháp nhưng kết
quả thu được là 1 <strong>generator</strong>.</p>
<h2>Vì sao thế?</h2>
<p>Tuple được tạo ra, ban đầu trông như 1 phiên bản thiếu tính năng của <code>list</code>,
nhưng mục đích tạo ra tuple là hoàn toàn khác. <code>tuple</code> được dùng như khái
niệm <code>struct</code> trong các ngôn ngữ khác, nó là tập hợp của các thông tin khác
nhau:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">14</span><span class="p">]:</span> <span class="n">student</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'Meo'</span><span class="p">,</span> <span class="mi">18</span><span class="p">,</span> <span class="s1">'0699609096'</span><span class="p">,</span> <span class="s1">'PyMi.vn'</span><span class="p">)</span>
</code></pre></div>
<p>và khi dùng thì unpack các giá trị ra:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">15</span><span class="p">]:</span> <span class="n">name</span><span class="p">,</span> <span class="n">age</span><span class="p">,</span> <span class="n">phone</span><span class="p">,</span> <span class="n">school</span> <span class="o">=</span> <span class="n">student</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">16</span><span class="p">]:</span> <span class="nb">print</span><span class="p">(</span><span class="n">school</span><span class="p">)</span>
<span class="n">PyMi</span><span class="o">.</span><span class="n">vn</span>
</code></pre></div>
<p>Còn list dùng để lặp qua/ duyệt qua:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">17</span><span class="p">]:</span> <span class="n">names</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'vuong'</span><span class="p">,</span> <span class="s1">'tron'</span><span class="p">,</span> <span class="s1">'tam giac'</span><span class="p">]</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">18</span><span class="p">]:</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">names</span><span class="p">:</span>
<span class="o">...</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="s1">'Hello các bạn mình là </span><span class="si">{}</span><span class="s1">'</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">name</span><span class="o">.</span><span class="n">title</span><span class="p">()))</span>
<span class="o">...</span><span class="p">:</span>
<span class="n">Hello</span> <span class="n">các</span> <span class="n">bạn</span> <span class="n">mình</span> <span class="n">là</span> <span class="n">Vuong</span>
<span class="n">Hello</span> <span class="n">các</span> <span class="n">bạn</span> <span class="n">mình</span> <span class="n">là</span> <span class="n">Tron</span>
<span class="n">Hello</span> <span class="n">các</span> <span class="n">bạn</span> <span class="n">mình</span> <span class="n">là</span> <span class="n">Tam</span> <span class="n">Giac</span>
</code></pre></div>
<p>Xem thêm tại <a href="https://docs.python.org/3/faq/design.html#why-are-there-separate-tuple-and-list-data-types">FAQ của
Python</a>.</p>
<p>Ok, vậy chẳng có nghĩa lý gì để "comprehension" ra tuple cả.</p>
<h2>Lập trình viên phê list comprehension túy lúy!</h2>
<p>Đã dùng là nghiện, mà nghiện là rất khó tỉnh. List comprehension hấp dẫn vậy
nên thường bị dùng SAI, lạm dụng. Một điều duy nhất quan trọng cần nhớ:</p>
<blockquote>
<p>List comprehension là để tạo ra list.</p>
</blockquote>
<p>Thế nhưng không ít lần, nó bị lạm dụng, khi người ta không cần đến list:</p>
<ul>
<li>khi chỉ cần đếm "số lượng phần tử"</li>
<li>để tính tổng</li>
<li>để in ra màn hình</li>
<li>để viết những vòng lặp điều kiện phức tạp.</li>
</ul>
<p>List comprehension tạo ra 1 list, vậy nên nếu không cần tới list kết quả đó
thì không nên dùng tới list comprehension mà hãy dùng vòng lặp for thông thường:</p>
<p>Đừng viết:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">19</span><span class="p">]:</span> <span class="p">[</span><span class="nb">print</span><span class="p">(</span><span class="n">name</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">names</span><span class="p">]</span>
<span class="n">VUONG</span>
<span class="n">TRON</span>
<span class="n">TAM</span> <span class="n">GIAC</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">19</span><span class="p">]:</span> <span class="p">[</span><span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">,</span> <span class="kc">None</span><span class="p">]</span>
</code></pre></div>
<p>Hãy viết:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">20</span><span class="p">]:</span> <span class="k">for</span> <span class="n">name</span> <span class="ow">in</span> <span class="n">names</span><span class="p">:</span>
<span class="o">...</span><span class="p">:</span> <span class="nb">print</span><span class="p">(</span><span class="n">name</span><span class="o">.</span><span class="n">upper</span><span class="p">())</span>
<span class="o">...</span><span class="p">:</span>
<span class="n">VUONG</span>
<span class="n">TRON</span>
<span class="n">TAM</span> <span class="n">GIAC</span>
</code></pre></div>
<p>Ta không tạo ra 1 cái list vô nghĩa chứa 3 phần tử <code>None</code> - tức là tiết kiệm bộ
nhớ (memory).</p>
<p>Đừng viết:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">21</span><span class="p">]:</span> <span class="nb">len</span><span class="p">([</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span><span class="mi">1000</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">])</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">21</span><span class="p">]:</span> <span class="mi">450</span>
</code></pre></div>
<p>Hãy viết:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">22</span><span class="p">]:</span> <span class="n">c</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">23</span><span class="p">]:</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">100</span><span class="p">,</span> <span class="mi">1000</span><span class="p">):</span>
<span class="o">...</span><span class="p">:</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="o">...</span><span class="p">:</span> <span class="n">c</span> <span class="o">=</span> <span class="n">c</span> <span class="o">+</span> <span class="mi">1</span>
<span class="o">...</span><span class="p">:</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">24</span><span class="p">]:</span> <span class="nb">print</span><span class="p">(</span><span class="n">c</span><span class="p">)</span>
<span class="mi">450</span>
</code></pre></div>
<p>vì ta sẽ không tạo ra 1 list (tạm) chứa 450 phần tử.</p>
<p>Và đừng viết:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">25</span><span class="p">]:</span> <span class="nb">sum</span><span class="p">([</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5000000</span><span class="p">)])</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">25</span><span class="p">]:</span> <span class="mi">12499997500000</span>
</code></pre></div>
<p>Mà hãy viết:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">26</span><span class="p">]:</span> <span class="n">sum_zero_to_5mil</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">27</span><span class="p">]:</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5000000</span><span class="p">):</span>
<span class="o">...</span><span class="p">:</span> <span class="n">sum_zero_to_5mil</span> <span class="o">=</span> <span class="n">sum_zero_to_5mil</span> <span class="o">+</span> <span class="n">i</span>
<span class="o">...</span><span class="p">:</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">28</span><span class="p">]:</span> <span class="n">sum_zero_to_5mil</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">28</span><span class="p">]:</span> <span class="mi">12499997500000</span>
</code></pre></div>
<p>bởi ta sẽ không <em>vô tình</em> tạo ra một list chiếm tới ~180MB bộ nhớ chỉ để tính
tổng.</p>
<p>Cách tính</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">32</span><span class="p">]:</span> <span class="kn">import</span> <span class="nn">sys</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">33</span><span class="p">]:</span> <span class="n">list_size</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">([</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5000000</span><span class="p">)])</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">34</span><span class="p">]:</span> <span class="n">all_numbers_size</span> <span class="o">=</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">(</span><span class="mi">5000000</span><span class="p">)</span> <span class="o">*</span> <span class="mi">5000000</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">35</span><span class="p">]:</span> <span class="nb">print</span><span class="p">(</span><span class="n">list_size</span><span class="p">,</span> <span class="n">all_numbers_size</span><span class="p">,</span> <span class="n">list_size</span> <span class="o">+</span> <span class="n">all_numbers_size</span><span class="p">)</span>
<span class="mi">40215168</span> <span class="mi">140000000</span> <span class="mi">180215168</span>
</code></pre></div>
<p>Và nếu như số vòng <code>for</code> hay lệnh <code>if</code> trong list comprehension nhiều hơn 2,3
nó không còn sạch sẽ và dễ đọc nữa, hãy quay trở về dùng vòng lặp <code>for</code> thông
thường. <a href="https://wikileaks.org/ciav7p1/cms/page_26607631.html">Ở CIA họ dạy thế</a>.</p>
<p>List/set/dict comprehension rất tiện để tạo list/set/dict, nhưng khi không
cần tới list/set/dict kết quả, hãy đừng lạm dụng.</p>
<h2>Generator</h2>
<p>Generator sinh ra các phần tử của list tương ứng, nhưng mỗi lần, chỉ có 1
phần tử được tạo ra, vì vậy thường dùng để tiết kiệm RAM. Cú pháp tạo generator
đơn giản chỉ là thay dấu <code>[]</code> thành <code>()</code>.</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">36</span><span class="p">]:</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">([</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5000000</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">])</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">36</span><span class="p">]:</span> <span class="mi">19836760</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">37</span><span class="p">]:</span> <span class="n">sys</span><span class="o">.</span><span class="n">getsizeof</span><span class="p">((</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5000000</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">))</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">37</span><span class="p">]:</span> <span class="mi">88</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">38</span><span class="p">]:</span> <span class="nb">sum</span><span class="p">([</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5000000</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">])</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">38</span><span class="p">]:</span> <span class="mi">5833329166668</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">39</span><span class="p">]:</span> <span class="nb">sum</span><span class="p">(</span><span class="n">i</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">5000000</span><span class="p">)</span> <span class="k">if</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">3</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">5</span> <span class="o">==</span> <span class="mi">0</span><span class="p">)</span> <span class="c1"># chỗ này mượn tạm luôn dấu () của sum</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">39</span><span class="p">]:</span> <span class="mi">5833329166668</span>
</code></pre></div>
<p>Đã đến lúc lạm dụng <strong>generator</strong>!!! nó được dùng mọi nơi trong Python3 và
là một cải tiến hấp dẫn của Python3 so với Python2.</p>
<h2>Hết.</h2>
<p>HVN at <a href="https://pymi.vn">https://pymi.vn</a> and http://www.familug.org</p>Bắt đầu lập trình CHICKEN Scheme2018-09-03T00:00:00+07:002018-09-03T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-09-03:/article/scm1/<p>Cu Hít ở xứ sở LISP - 1 trong 2 ngôn lập trình lâu đời nhất trái đất.</p><p>Ba năm trước, khi chúng tôi bắt đầu mở khóa học đầu tiên dạy lập trình Python tại
<a href="https://pymi.vn">PyMi</a>, có 8 học viên đi học, hầu hết đều là sysadmin của một
công ty nào đó từng động tới OpenStack/SaltStack/Ansible.
Mới chỉ 3 năm trước, khi những người nghe tới Python tại Việt Nam
là những "hàng hiếm", thì tại thời điểm này, 2018, Python nhan nhản mọi nơi,
các trường (đại) học bắt đầu đưa Python vào chương trình giảng dạy, các
công ty tuyển Python như điên dại, làm Machine Learning, làm Odoo...
các chị em kế toán, ngân hàng bắt đầu kéo nhau đi học code Python!</p>
<p>Hầu hết số đông đến với Python không phải vì ham mê lập trình, yêu thích những
dòng code sạch sẽ, mà đơn giản bởi (1) công việc yêu cầu (2) kiếm tiền.
Khi đạt được múc đích rồi thì sao? đơn giản là thì thôi. Việc này không có gì
sai cả.</p>
<p>Vậy còn những người ham mê lập trình, yêu thích code thì làm gì? Họ tiếp tục
đào sâu thêm, học thêm vô vàn kiến thức trong thế giới lập trình, mở rộng
ra những ngôn ngữ mới, cách viết code mới, cách suy nghĩ mới.</p>
<p>Python thành công bởi ngôn ngữ sạch sẽ, (dẫn tới) cộng đồng đông đảo, (dẫn
tới) các thư viện có sẵn rất nhiều, chất lượng chuẩn, cập nhật hàng ngày.
Hầu hết các ngôn ngữ bạn từng nghe tên sẽ nằm trong nhóm này, những ngôn ngữ
còn lại (có tới hàng ngàn ngôn ngữ lập trình) thường bị xem là "đã chết".
Một ngôn ngữ mã nguồn mở chỉ thực sự chết khi cộng đồng không còn một ai,
những ngôn ngữ bạn chưa nghe tên bao giờ có khi đơn giản vì bạn không quen
ai dùng ngôn ngữ đó.</p>
<p>Python sinh năm 1991, C sinh năm 197x, thì LISP sinh ra vào năm 1958 (60 năm
trước - chỉ sau Fortran 1 năm - <a href="https://www.familug.org/2016/02/python-python-tuoi-gi.html">xem bảng tuổi các ngôn
ngữ</a> ).
Hẵn rất dễ cho rằng LISP đã chết, vì chẳng gặp ai ở Việt Nam dùng nó. Cho tới
khi bạn gặp một anh kỹ sư xây dựng dùng AutoCAD vẽ bản vẽ 2D thiết kế nhà,
và cộng đồng này vẫn truyền tay nhau những đoạn code LISP.</p>
<p>Cộng đồng quốc tế rất đánh giá cao LISP, và có những thời điểm lịch sử, LISP
được gọi là ngôn ngữ của AI (một hướng phát triển AI rất khác so với
Machine Learning/Deep Learning sau này).</p>
<p>LISP (<strong>LIS</strong>t <strong>P</strong>rocessor) là tên của một gia đình các ngôn ngữ lập trình,
có 2 nhánh (dialect) chính là Common Lisp và Scheme. Cả 2 dialect này đều có
đặc điểm chung sử dụng rất nhiều dấu <code>()</code> nhưng khi thò tay vào code thì lại có
rất nhiều điểm khác. Một ví dụ viết function tính số gấp đôi của số đầu vào
bằng CHICKEN Scheme (một bản Scheme <em>còn sống</em>):</p>
<div class="highlight"><pre><span></span><code><span class="p">(</span><span class="k">define </span><span class="p">(</span><span class="nf">double</span> <span class="nv">x</span><span class="p">)</span>
<span class="p">(</span><span class="nb">* </span><span class="nv">x</span> <span class="mi">2</span><span class="p">)</span>
<span class="p">)</span>
</code></pre></div>
<p>Gọi function:</p>
<div class="highlight"><pre><span></span><code><span class="p">(</span><span class="k">define </span><span class="nv">n</span> <span class="mi">21</span><span class="p">)</span>
<span class="p">(</span><span class="nf">double</span> <span class="nv">n</span><span class="p">)</span>
</code></pre></div>
<p>So với Python:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">double</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
<span class="k">return</span> <span class="n">x</span> <span class="o">*</span> <span class="mi">2</span>
<span class="n">n</span> <span class="o">=</span> <span class="mi">21</span>
<span class="n">double</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
</code></pre></div>
<p>Nếu xét kỹ, cú pháp của Scheme (<code>define</code>) ít khái niệm/ký tự/nhất quán hơn
Python (<code>:</code>, <code>def</code>, <code>=</code>, <code>return</code>).</p>
<p>Một khái niệm khác rất quan trọng/phổ biến/nổi bật của LISP là sử dụng
macro (cùng khái niệm Macro trong C - nhưng mạnh mẽ hơn, ở một đẳng cấp hoàn
toàn khác).</p>
<h2>Các dialect của LISP</h2>
<h3>Common Lisp</h3>
<p>Common Lisp để tồn tại sau nhiều năm lịch sử với sự cạnh tranh của hàng ngàn ngôn
ngữ lập trình khác, đã tự biến đổi mình như một sinh vật tiến hóa. Nó gom vào
mình cả lịch sử lẫn những mảnh ghép của cộng đồng, khiến cho Common Lisp khá
đồ sộ, thực dụng. Những từ khóa cơ bản trong Common Lisp như <code>cdr</code>, <code>car</code>
là viết tắt của những khái niệm phổ biến những năm 195x nhưng ngày nay không
còn được nhắc tới, cho nên Common Lisp hơi khó đọc đối với dân ngoại đạo.
Bộ tiêu chuẩn Common Lisp dài <a href="https://www.techstreet.com/standards/incits-226-1994-r1999?product_id=56214">hơn 1000 trang
giấy</a></p>
<p>Bản chạy opensource phổ biến nhất của CL là : <a href="http://www.sbcl.org/">SBCL</a>
một số bản thương mại rất đắt tiền.</p>
<h3>Scheme</h3>
<p>Scheme ra đời vào những năm 197X, với mong muốn có một bản LISP trong sạch,
dễ đọc, nhỏ gọn, không còn chứa những di tích lịch sử, không còn những từ khóa
khó hiểu như <code>cdr</code>, <code>car</code>. Scheme được dùng làm ngôn ngữ chính
để dạy học lập trình tại đại học danh giá MIT - nổi tiếng theo đó là SICP
- cuốn sách luôn nằm trong top 10 các cuốn sách về lập trình (xem cuối bài).</p>
<p>NOTE: sau này vì lý do thực dụng, MIT dạy SICP bằng Python, không dùng Scheme
nữa.</p>
<p>Scheme được thống nhất thành tiêu chuẩn viết tắt là R5RS, R6RS, R7RS
hay đầy đủ là <strong>Revised^5 Report on the Algorithmic Language Scheme</strong> -
<strong>R</strong>evised Revised Revised Revised Revised <strong>R</strong>eport ... <strong>S</strong>cheme.
Mỗi lần "revised" như vậy (tăng lên 1 số),
tiêu chuẩn lại thêm bớt gì đó, và chuyện này không phải ai cũng đồng ý, nên
nhiều bản chạy (implementation) quyết định chỉ
hỗ trợ R5RS mà ko chịu lên R6.</p>
<p>Các bản Scheme nổi bật:</p>
<ul>
<li>CHICKEN scheme</li>
<li>Guile (cài sẵn trên các máy Ubuntu)</li>
<li>Chez scheme</li>
<li>Gambit</li>
<li>MIT Scheme</li>
</ul>
<h3>Clojure</h3>
<p>Clojure nổi bật là 1 LISP-dialect HIỆN ĐẠI. Nó chạy trên JVM (như Java)
và được dùng phổ biến trong các doanh nghiệp công nghệ - tại thời điểm
năm 2018. Xem thêm tại <a href="https://clojure.org/about/lisp">trang chủ của Clojure</a>.</p>
<h3>Racket</h3>
<p>Racket từng là PLT Scheme, từng tuân theo tiêu chuẩn Scheme (S_RS) cho tới khi
nó tiến hóa thành một thứ khác rất nhiều. Racket cũng là một ngôn
ngữ hiện đại trong gia đình LISP.</p>
<h3>Emacs Lisp</h3>
<p>LISP dialect dùng để phát triển các tính năng cho editor Emacs - một chương
trình chỉnh sửa code lâu đời, được ưa thích trong giới lập trình viên.</p>
<h2>Học để làm gì?</h2>
<ul>
<li>Vui</li>
<li>Mở mang suy nghĩ, thay đổi cách nghĩ - khi trên tay bạn chỉ có một cái búa
thì mọi thứ bạn thấy đều là cái đinh.</li>
<li>Để thực hành khi đọc những cuốn sách hay nổi tiếng: SICP, HTDP</li>
</ul>
<h3>Có để làm được không?</h3>
<ul>
<li>
<p>Cái này tùy vào chuyện bạn làm gì. Nếu lập trình nhúng có thể bạn sẽ dùng 1
LISP dialect X, nhưng viết micro service tại các doanh nghiệp dùng Java,
Clojure lại là lựa chọn hấp dẫn hơn. Có thể tham khảo hướng dẫn học
Common Lisp năm 2018 <a href="http://stevelosh.com/blog/2018/08/a-road-to-common-lisp/">A road to Common
Lisp</a></p>
</li>
<li>
<p>Những người học dùng LISP thường đã có công việc ổn định, không còn lo học
ngôn ngữ đầu tiên để xin việc - vậy nên chuyện có mang đi làm được hay không
không quá quan trọng ở đây. Học LISP xong, theo một cách nào đó, sẽ khiến
bạn trở thành lập trình viên Python giỏi hơn.</p>
</li>
</ul>
<p>Loạt bài này sử dụng <a href="http//call-cc.org">CHICKEN scheme</a>.</p>
<h2>Cài đặt</h2>
<p>Trên trang chủ của các bản LISP đều có hướng dẫn cài đặt chi tiết.
Trên ubuntu 16.04, <code>guile</code> có sẵn, cài CHICKEN scheme chỉ bằng một câu lệnh:</p>
<div class="highlight"><pre><span></span><code>sudo apt install -y chicken-bin
</code></pre></div>
<p>Lệnh này cài bản <code>Version: 4.9.X</code>, cài xong bật lên để gõ code như python
interpreter:</p>
<div class="highlight"><pre><span></span><code><span class="o">$</span> <span class="n">csi</span>
<span class="n">CHICKEN</span>
<span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="mi">2008</span><span class="o">-</span><span class="mi">2014</span><span class="p">,</span> <span class="n">The</span> <span class="n">Chicken</span> <span class="n">Team</span>
<span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="mi">2000</span><span class="o">-</span><span class="mi">2007</span><span class="p">,</span> <span class="n">Felix</span> <span class="n">L</span><span class="o">.</span> <span class="n">Winkelmann</span>
<span class="n">Version</span> <span class="mf">4.9</span><span class="o">.</span><span class="mf">0.1</span> <span class="p">(</span><span class="n">stability</span><span class="o">/</span><span class="mf">4.9</span><span class="o">.</span><span class="mi">0</span><span class="p">)</span> <span class="p">(</span><span class="n">rev</span> <span class="mi">8</span><span class="n">b3189b</span><span class="p">)</span>
<span class="n">linux</span><span class="o">-</span><span class="n">unix</span><span class="o">-</span><span class="n">gnu</span><span class="o">-</span><span class="n">x86</span><span class="o">-</span><span class="mi">64</span> <span class="p">[</span> <span class="mi">64</span><span class="n">bit</span> <span class="n">manyargs</span> <span class="n">dload</span> <span class="n">ptables</span> <span class="p">]</span>
<span class="n">bootstrapped</span> <span class="mi">2014</span><span class="o">-</span><span class="mi">06</span><span class="o">-</span><span class="mi">07</span>
<span class="c1">#;1> (+ 1 2 3 4 5)</span>
<span class="mi">15</span>
</code></pre></div>
<p>Để hỗ trợ chỉnh sửa tiện hơn, trên Ubuntu nên cài thêm <code>rl-wrap</code> và chạy
<code>rl-wrap csi</code>.</p>
<h2>Tài liệu</h2>
<p>Sách free online thì có 2 cuốn đã lừng danh</p>
<ul>
<li><a href="https://mitpress.mit.edu/sites/default/files/sicp/index.html">SICP - Structure and Interpretation of Computer Programs</a></li>
<li><a href="https://htdp.org/2018-01-06/Book/">HTDP - How to Design Programs</a></li>
</ul>
<p>Các bài viết</p>
<ul>
<li><a href="http://www.draketo.de/proj/py2guile/">"Nhật ký" từ Python qua Guile Scheme</a></li>
<li>Lập trình viên Python có thể xem phần hướng dẫn cơ bản dành riêng cho Python
developer trên <a href="http://wiki.call-cc.org/chicken-for-python-programmers">wiki của CHICKEN</a></li>
<li><a href="https://www.artima.com/weblogs/viewpost.jsp?thread=251474">Hành trình của Python dev vào thế giới Scheme</a></li>
</ul>
<p>Trên đây là tài liệu về Scheme, tài liệu Common Lisp cũng không thiếu,
và đều online/free - xem tại <a href="http://stevelosh.com/blog/2018/08/a-road-to-common-lisp/">A road to Common
Lisp</a></p>
<h2>Emacs</h2>
<p>Khi lập trình LISP, người dùng được khuyên sử dụng Emacs đặc biệt là
common lisp với SLIME. Đây không phải điều bắt buộc, nhưng nếu chưa từng dùng
Emacs, đây là một cơ hội tuyệt vời để trải nghiệm (vì emacs dùng Emacs Lisp).</p>
<p>Hết phần 1.</p>Làm app giao diện đồ hoạ với Python2018-08-18T00:00:00+07:002018-08-18T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-08-18:/article/gui/<p>Không gì là không thể - đã dễ, lại còn đẹp</p><p>Giao diện đồ hoạ (GUI - Graphic User Interface) vốn từng là một phần không thể thiếu khi nói về lập trình. Dù học ngôn ngữ lập trình nào, người ta cũng nghĩ tới chuyện "làm sao để có giao diện đồ hoạ".</p>
<p>Thế giới thay đổi, thứ từng quan trọng của ngày hôm qua thì hôm nay chưa chắc đã cần tới. Thời đại tất cả mọi thứ đều chuyển lên web, thì web/app mobile trở thành giao diện để tương tác với người dùng, chứ không phải các phần mềm có giao diện chạy trên máy tính như trước kia.
Giờ đây người ta: nghe nhạc trên web, xem film trên web, chơi game trên web, soạn thảo văn bản trên web... khó còn ứng dụng nào không đưa lên web nữa. Vậy nên về mặt "sự nghiệp", có vẻ như bạn nên đầu tư vào kỹ năng làm web thay vì học để tạo một app trên desktop như cách đây chục năm.</p>
<p>Dĩ nhiên, GUI không ngay lập tức biến mất, vẫn có nhu cầu sử dụng, vẫn có người dùng, vẫn có hàng tá thư viện đồ hoạ tồn tại từ lâu (và vẫn tiếp tục phát triển), vẫn có những game mà chỉ chơi được trên máy tính do yêu cầu về hiệu năng mà web không đáp ứng nổi (như Half-Life/ đế chế / đua xe ...).</p>
<p>Python hỗ trợ không ít các thư viện làm GUI app, có thể kể tới: Qt, WxWidgets, Tkinter, <a href="https://kivy.org/">Kivy (làm cả app mobile)</a>... xem đầy đủ tại:</p>
<ul>
<li><a href="">https://docs.python.org/3/faq/gui.html</a></li>
<li><a href="">https://www.python.org/about/apps/#desktop-guis</a></li>
<li><a href="">https://docs.python-guide.org/scenarios/gui/</a></li>
</ul>
<p><a href="https://en.wikipedia.org/wiki/Qt_(software)">Qt</a> là nền tảng phát triển ứng dụng dùng trong công nghiệp, hỗ trợ mọi hệ điều hành phổ biến, và rất "xịn". Nếu có nhu cầu làm ứng dụng desktop với Python, hãy đầu tư vào Qt để có một sản phẩm đẳng cấp, không kém bất kỳ nền tảng nào khác.</p>
<p>Tk là hệ thống thư viện đồ hoạ đơn giản, dễ dùng, chạy trên cả 3 hệ điều hành phổ biến: Windows, Ubuntu, OSX/MacOS và điều quan trọng nhất: thư viện <code>tkinter</code> đi kèm mọi bộ cài Python, nên muốn dùng không cần phải cài đặt gì thêm.</p>
<p>Bài viết hướng dẫn tạo một chương trình đồ hoạ sử dụng <code>tkinter</code> với Python 3.6, thực hiện trên MacOS Sierra (10.12.6).</p>
<h2>Khái niệm cơ bản về một chương trình giao diện đồ hoạ.</h2>
<p>Một chương trình có giao diện đồ hoạ là một chương trình luôn chạy cho tới khi người dùng thoát chương trình. Dễ suy ra ở đây có chạy 1 vòng lặp vô hạn để luôn hiển thị giao diện (gọi là main loop). Chương trình này hoạt động dựa trên những tương tác của người dùng và phản ứng với các tương tác đó (bấm nút này thì chạy cái kia). Loại chương trình như vậy thuộc loại mô hình "Event-driven programming".</p>
<p>Các thao tác của người dùng được gọi là các <strong>event</strong>,
các hành động tương ứng của chương trình (các function) được gọi là các <strong>callback</strong>, gắn vào các bộ phận giao diện. Gắn callback vào nút bấm thì khi ta bấm nút, callback sẽ được gọi.</p>
<p>Các bộ phận giao diện như nút bấm, chữ, ô nhập ký tự ... được gọi là các <strong>widget</strong>.</p>
<h2>Lập trình GUI với Tkinter</h2>
<p>Code gõ trực tiếp trên IPython:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># on Ubuntu, requires install: `sudo apt-get install -y python3-tk`</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">1</span><span class="p">]:</span> <span class="kn">import</span> <span class="nn">tkinter</span> <span class="k">as</span> <span class="nn">tk</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">2</span><span class="p">]:</span> <span class="n">tk</span><span class="o">.</span><span class="n">Frame</span><span class="p">(</span><span class="n">tk</span><span class="o">.</span><span class="n">Tk</span><span class="p">())</span><span class="o">.</span><span class="n">mainloop</span><span class="p">()</span>
</code></pre></div>
<p>Nếu Tk hoat động trên máy bạn, ngay lập tức một cửa sổ trắng tinh sẽ hiện ra.</p>
<p><img alt="emptygui" src="http://pp.pymi.vn/images/gui.png"></p>
<p><code>Tk()</code> tạo một cửa sổ chính (main window), <code>Frame</code> là một widget có khả năng chứa các widget khác. Gọi function <code>mainloop()</code> để chạy giao diện mãi mãi cho tới khi người dùng đóng lại.</p>
<h3>Các widget</h3>
<p>Tkinter có sẵn 17 widget:</p>
<p><code>Button</code> <code>Canvas</code> <code>Checkbutton</code> <code>Entry</code> <code>Frame</code> <code>Label</code> <code>Listbox</code> <code>Menu</code> <code>Menubutton</code> <code>Message</code> <code>Radiobutton</code> <code>Scale</code> <code>Scrollbar</code> <code>Text</code> <code>Spinbox</code> <code>LabelFrame</code> <code>PanedWindow</code></p>
<p>Xem ví dụ hello-world tại <a href="https://docs.python.org/3/library/tkinter.html#a-simple-hello-world-program">đây</a>.</p>
<p>Sau đây ta viết một GUI app có hiển thị tiêu đề (Label), có một ô nhập địa chỉ trang web (Entry), có một nút bấm để kiểm tra status của trang web (Button).</p>
<div class="highlight"><pre><span></span><code><span class="c1"># on Ubuntu, requires install: `sudo apt-get install -y python3-tk`</span>
<span class="kn">import</span> <span class="nn">tkinter</span> <span class="k">as</span> <span class="nn">tk</span>
<span class="kn">import</span> <span class="nn">urllib.request</span>
<span class="k">class</span> <span class="nc">Application</span><span class="p">(</span><span class="n">tk</span><span class="o">.</span><span class="n">Frame</span><span class="p">):</span>
<span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">master</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="n">master</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">pack</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">create_widgets</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">create_widgets</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">label</span> <span class="o">=</span> <span class="n">tk</span><span class="o">.</span><span class="n">Label</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="s2">"PyMi.vn checker"</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">label</span><span class="o">.</span><span class="n">pack</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">entrythingy</span> <span class="o">=</span> <span class="n">tk</span><span class="o">.</span><span class="n">Entry</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">entrythingy</span><span class="o">.</span><span class="n">pack</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">contents</span> <span class="o">=</span> <span class="n">tk</span><span class="o">.</span><span class="n">StringVar</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">entrythingy</span><span class="p">[</span><span class="s2">"textvariable"</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">contents</span>
<span class="bp">self</span><span class="o">.</span><span class="n">entrythingy</span><span class="o">.</span><span class="n">bind</span><span class="p">(</span><span class="s2">"<Key-Return>"</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">check_site</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">hi_there</span> <span class="o">=</span> <span class="n">tk</span><span class="o">.</span><span class="n">Button</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">hi_there</span><span class="p">[</span><span class="s2">"text"</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span>
<span class="s2">"Check web site up/down."</span> <span class="s2">" Enter URL with http(s):"</span>
<span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">hi_there</span><span class="p">[</span><span class="s2">"command"</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">check_site</span>
<span class="bp">self</span><span class="o">.</span><span class="n">hi_there</span><span class="o">.</span><span class="n">pack</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">quit</span> <span class="o">=</span> <span class="n">tk</span><span class="o">.</span><span class="n">Button</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">text</span><span class="o">=</span><span class="s2">"QUIT"</span><span class="p">,</span> <span class="n">command</span><span class="o">=</span><span class="n">root</span><span class="o">.</span><span class="n">destroy</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">quit</span><span class="o">.</span><span class="n">pack</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">check_site</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">event</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="n">url</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">contents</span><span class="o">.</span><span class="n">get</span><span class="p">()</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span> <span class="ow">or</span> <span class="s2">"https://pymi.vn"</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">url</span><span class="o">.</span><span class="n">startswith</span><span class="p">(</span><span class="s2">"http"</span><span class="p">):</span>
<span class="n">url</span> <span class="o">=</span> <span class="s2">"http://</span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>
<span class="c1"># fake useragent as cloudflare block python agent</span>
<span class="n">r</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">Request</span><span class="p">(</span>
<span class="n">url</span><span class="p">,</span>
<span class="n">method</span><span class="o">=</span><span class="s2">"HEAD"</span><span class="p">,</span>
<span class="n">headers</span><span class="o">=</span><span class="p">{</span><span class="s2">"User-Agent"</span><span class="p">:</span> <span class="s2">"python-requests/2.23.0"</span><span class="p">},</span>
<span class="p">)</span>
<span class="n">resp</span> <span class="o">=</span> <span class="n">urllib</span><span class="o">.</span><span class="n">request</span><span class="o">.</span><span class="n">urlopen</span><span class="p">(</span><span class="n">r</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">2</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"response: </span><span class="si">{}</span><span class="s2"> </span><span class="si">{}</span><span class="s2">"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">resp</span><span class="o">.</span><span class="n">status</span><span class="p">,</span> <span class="n">resp</span><span class="o">.</span><span class="n">url</span><span class="p">))</span>
<span class="n">root</span> <span class="o">=</span> <span class="n">tk</span><span class="o">.</span><span class="n">Tk</span><span class="p">()</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Application</span><span class="p">(</span><span class="n">master</span><span class="o">=</span><span class="n">root</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">master</span><span class="o">.</span><span class="n">title</span><span class="p">(</span><span class="s2">"My checker app"</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">master</span><span class="o">.</span><span class="n">minsize</span><span class="p">(</span><span class="mi">300</span><span class="p">,</span> <span class="mi">200</span><span class="p">)</span>
<span class="n">app</span><span class="o">.</span><span class="n">mainloop</span><span class="p">()</span>
</code></pre></div>
<p>Code trên:</p>
<ul>
<li>phần init chỉ là thủ tục</li>
<li>tạo một label với text cần hiển thị</li>
<li>tạo entry để người dùng nhập nội dung, gán giá trị người dùng nhập vào <code>self.contents</code>, bind widget entry với phím Enter (trên MacOS gọi là return), và chạy method <code>check_site</code> khi người dùng gõ enter.</li>
<li>tạo nút bấm với dòng chữ "Check site", gọi method <code>check_site</code> khi người dùng bấm nút</li>
<li>tạo nút bấm "Quit" để thoát chương trình</li>
<li>set thanh tiêu đề <code>title</code> và kích thước cho chương trình qua <code>app.master</code>.</li>
</ul>
<p>Kết quả:</p>
<p><img alt="checker" src="http://pp.pymi.vn/images/checker.png"></p>
<p>Danh sách đầy đủ các widget xem tại <a href="https://github.com/python/cpython/blob/3.6/Lib/tkinter/__init__.py">https://github.com/python/cpython/blob/3.6/Lib/tkinter/<strong>init</strong>.py</a> - search <code>(Widget</code> (các subclass của class Widget).</p>
<p>Thử các ví dụ có sẵn đi kèm với Python:</p>
<div class="highlight"><pre><span></span><code># on Ubuntu, requires install: sudo apt-get install -y python3-examples
python3 -m turtledemo.minimal_hanoi
python3 -m turtledemo.sorting_animate
</code></pre></div>
<p>hay xem code 1 IDE viết bằng tk: <a href="https://github.com/thonny/thonny/tree/v3.3.4">thonny</a></p>
<h2>Kết luận</h2>
<p>Tk nhẹ gọn, có sẵn, dễ dùng, đủ dùng khi bạn thấy đủ. Bao giờ thấy ngột ngạt, có lẽ lại chuyển sang Qt, các khái niệm lập trình giao diện dù dùng library/framework nào cũng đều tương tự nhau.</p>
<h2>Tham khảo</h2>
<ul>
<li>Tkinter: https://docs.python.org/3/library/tkinter.html</li>
</ul>
<p>Hết.
HVN @ <a href="">https://pymi.vn</a> and <a href="">https://www.familug.org/</a></p>Viết một chatbot đơn giản với Python32018-06-05T00:00:00+07:002018-06-05T00:00:00+07:00tung491tag:pp.pymi.vn,2018-06-05:/article/viet-mot-chatbot-don-gian-voi-python3/<p>Làm FaceBook chatbot còn dễ hơn rủ em gái bàn bên đi xem phim...</p><h2>Chatbot là gì?</h2>
<p>Trước khi thò tay vào hì hục code, ta cần hiểu chatbot là gì đã?</p>
<p><a href="https://en.wikipedia.org/wiki/Chatbot">Chatbot</a> là một chương trình thực hiện cuộc hội thoại qua phương pháp gửi nhận văn bản hoặc các object như hình ảnh, file, ... Chú ý Chatbot không nhất thiết là phải thông minh, là phải dùng trí tuệ nhân tạo, etc ...</p>
<p>Có bao giờ sắp đến giao thừa hay một dịp mà bạn muốn nhắn tin cho nhiều người vào 12h đêm mà bạn không thể dậy được, hoặc bạn quá lười để làm một việc lặp đi lặp lại? Câu trả lời là viết một chatbot và hẹn giờ cho nó.</p>
<h2>Viết chatbot</h2>
<p>Trong bài viết này mình sử dụng 2 thư viện có sẵn trên mạng là <a href="https://fbchat.readthedocs.io/en/master/">fbchat</a>, <a href="https://schedule.readthedocs.io/en/stable/">schedule</a> do đó bạn cần tạo <a href="http://pymi.vn/blog/virtualenv/">virtualenv</a> trước tiên, sau đó dùng pip để cài 2 lib trên rồi tạo một file code python tùy ý, ở đây mình dùng <code>chatbot.py</code>.</p>
<p>Đầu tiên, import những lib mình cần 🎉</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">threading</span> <span class="kn">import</span> <span class="n">Thread</span>
<span class="kn">from</span> <span class="nn">fbchat</span> <span class="kn">import</span> <span class="n">Client</span>
<span class="kn">from</span> <span class="nn">fbchat.models</span> <span class="kn">import</span> <span class="n">Message</span><span class="p">,</span> <span class="n">ThreadType</span>
<span class="kn">import</span> <span class="nn">schedule</span>
</code></pre></div>
<p>Sau đó tạo một class <code>Bot</code> kế thừa <code>Client</code>:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Bot</span><span class="p">(</span><span class="n">Client</span><span class="p">):</span>
</code></pre></div>
<p>Tạo 1 function trong class <code>Bot</code> để thực hiện gửi tin nhắn, dưới đây là code của mình:</p>
<div class="highlight"><pre><span></span><code><span class="k">class</span> <span class="nc">Bot</span><span class="p">(</span><span class="n">Client</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">do_something</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c1">#Đổi tên function cho phù hợp</span>
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
<span class="n">lst_id</span> <span class="o">=</span> <span class="p">[</span><span class="o">...</span><span class="p">]</span> <span class="c1"># List chứa fb id của những người bạn muốn gửi</span>
<span class="k">for</span> <span class="n">user_id</span> <span class="ow">in</span> <span class="n">lst_id</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">Message</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="s2">"Chúc mừng năm mới"</span><span class="p">),</span>
<span class="n">thread_id</span><span class="o">=</span><span class="n">user_id</span><span class="p">,</span> <span class="n">thread_type</span><span class="o">=</span><span class="n">ThreadType</span><span class="o">.</span><span class="n">USER</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sendLocalImage</span><span class="p">(</span><span class="s1">'/home/dosontung007/Pictures/wallpaper.png'</span><span class="p">,</span> <span class="n">message</span><span class="o">=</span><span class="n">Message</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="s1">'Chúc mừng năm mới'</span><span class="p">),</span>
<span class="n">thread_id</span><span class="o">=</span><span class="n">user_id</span><span class="p">,</span> <span class="n">thread_type</span><span class="o">=</span><span class="n">ThreadType</span><span class="o">.</span><span class="n">USER</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Sent success to </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="nb">str</span><span class="p">(</span><span class="n">user_id</span><span class="p">))</span>
</code></pre></div>
<p>Và để nhận được tin nhắn từ những người gửi cho mình cho mình , ta viết function <code>onMessage</code> trong class <code>Bot</code> và xử lí các tin nhắn đó:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">onMessage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message_object</span><span class="p">,</span> <span class="n">author_id</span><span class="p">,</span> <span class="n">thread_id</span><span class="p">,</span> <span class="n">thread_type</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">lst_msg</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="s1">'Chúc mừng năm mới'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">author_id</span> <span class="o">!=</span> <span class="bp">self</span><span class="o">.</span><span class="n">uid</span> <span class="ow">and</span> <span class="n">message_object</span><span class="o">.</span><span class="n">text</span> <span class="ow">in</span> <span class="n">lst_msg</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">Message</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="s1">'Năm mới chúc .....'</span><span class="p">),</span>
<span class="n">thread_id</span><span class="o">=</span><span class="n">author_id</span><span class="p">,</span>
<span class="n">thread_type</span><span class="o">=</span><span class="n">thread_type</span><span class="p">)</span>
</code></pre></div>
<p>Tham khảo thêm tại https://fbchat.readthedocs.io/en/master/</p>
<p>Job thực hiện việc gửi tin nhắn trong này đó là:</p>
<p><code>Bot(os.environ['USERNAME_'], os.environ['PASSWORD']).do_something()</code></p>
<p>Class <code>Bot</code> kế thừa <code>Client</code>, khi tạo một Bot object, ta cần truyền 2 tham số là username và password của Facebook của bạn. Do đó bạn cần set value cho 2 var <code>USERNAME_</code> và <code>PASSWORD</code> bằng câu lệnh <code>export var=value</code> trong bash trước khi chạy chương trình (vì ta không muốn ghi trực tiếp password vào file code - lộ mật khẩu nếu up lên GitHub). Lưu ý <code>USERNAME_</code> chứ không phải <code>USERNAME</code>.</p>
<p>Bây giờ còn một công việc duy nhất là hẹn giờ cho job làm việc thôi!</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">job</span><span class="p">():</span>
<span class="n">Bot</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'USERNAME_'</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'PASSWORD'</span><span class="p">])</span><span class="o">.</span><span class="n">do_something</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">send_msg</span><span class="p">():</span>
<span class="n">schedule</span><span class="o">.</span><span class="n">every</span><span class="p">()</span><span class="o">.</span><span class="n">day</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="s1">'00:00'</span><span class="p">)</span><span class="o">.</span><span class="n">do</span><span class="p">(</span><span class="n">job_that_executes_once</span><span class="p">))</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">schedule</span><span class="o">.</span><span class="n">run_pending</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
</code></pre></div>
<p>Thay đổi <code>00:00</code> bằng thời gian mà bạn muốn hẹn giờ.</p>
<p>Để nhận được message, ta sử dụng function <code>listen</code> từ <code>Client</code> , về cơ bản <code>listen</code> khi chạy sẽ truyền các arguments vào <code>onMessage</code> mỗi lần Facebook bạn có event mới (VD: có người nhắn cho bạn, bạn nhắn cho người khác hoặc tin nhắn trong nhóm, ...):</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">reply_msg</span><span class="p">():</span>
<span class="n">Bot</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'USERNAME_'</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'PASSWORD'</span><span class="p">])</span><span class="o">.</span><span class="n">listen</span><span class="p">()</span>
</code></pre></div>
<p>Ở function main, mình sử dụng lib threading để chạy song song 2 job là reply_msg và send_msg :</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">send_msg</span><span class="p">)</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
<span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">reply_msg</span><span class="p">)</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
</code></pre></div>
<p>Cuối cùng cũng xong 🎉.Sau tất cả, đây là một con chatbot hoàn chỉnh :</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">logging</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">from</span> <span class="nn">threading</span> <span class="kn">import</span> <span class="n">Thread</span>
<span class="kn">from</span> <span class="nn">fbchat</span> <span class="kn">import</span> <span class="n">Client</span>
<span class="kn">from</span> <span class="nn">fbchat.models</span> <span class="kn">import</span> <span class="n">Message</span><span class="p">,</span> <span class="n">ThreadType</span>
<span class="kn">import</span> <span class="nn">schedule</span>
<span class="k">class</span> <span class="nc">Bot</span><span class="p">(</span><span class="n">Client</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">onMessage</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">message_object</span><span class="p">,</span> <span class="n">author_id</span><span class="p">,</span> <span class="n">thread_id</span><span class="p">,</span> <span class="n">thread_type</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="n">lst_msg</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="s1">'Chúc mừng năm mới'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">author_id</span> <span class="o">!=</span> <span class="bp">self</span><span class="o">.</span><span class="n">uid</span> <span class="ow">and</span> <span class="n">message_object</span><span class="o">.</span><span class="n">text</span> <span class="ow">in</span> <span class="n">lst_msg</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">Message</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="s1">'Năm mới chúc .....'</span><span class="p">),</span>
<span class="n">thread_id</span><span class="o">=</span><span class="n">author_id</span><span class="p">,</span>
<span class="n">thread_type</span><span class="o">=</span><span class="n">ThreadType</span><span class="o">.</span><span class="n">USER</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">do_something</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="n">logging</span><span class="o">.</span><span class="n">basicConfig</span><span class="p">(</span><span class="n">level</span><span class="o">=</span><span class="n">logging</span><span class="o">.</span><span class="n">INFO</span><span class="p">)</span>
<span class="n">user_ids</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'100012610305665'</span><span class="p">]</span>
<span class="k">for</span> <span class="n">user_id</span> <span class="ow">in</span> <span class="n">user_ids</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">Message</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="s2">"Chúc mừng năm mới"</span><span class="p">),</span>
<span class="n">thread_id</span><span class="o">=</span><span class="n">user_id</span><span class="p">,</span> <span class="n">thread_type</span><span class="o">=</span><span class="n">ThreadType</span><span class="o">.</span><span class="n">USER</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sendLocalImage</span><span class="p">(</span><span class="s1">'/home/dosontung007/Pictures/wallpaper.png'</span><span class="p">,</span> <span class="n">message</span><span class="o">=</span><span class="n">Message</span><span class="p">(</span><span class="n">text</span><span class="o">=</span><span class="s1">'Chúc mừng năm mới'</span><span class="p">),</span>
<span class="n">thread_id</span><span class="o">=</span><span class="n">user_id</span><span class="p">,</span> <span class="n">thread_type</span><span class="o">=</span><span class="n">ThreadType</span><span class="o">.</span><span class="n">USER</span><span class="p">)</span>
<span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s1">'Sent success to </span><span class="si">%s</span><span class="s1">'</span> <span class="o">%</span> <span class="s2">"100012610305665"</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">job_that_executes_once</span><span class="p">():</span>
<span class="n">Bot</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'USERNAME_'</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'PASSWORD'</span><span class="p">])</span><span class="o">.</span><span class="n">something</span><span class="p">()</span>
<span class="k">return</span> <span class="n">schedule</span><span class="o">.</span><span class="n">CancelJob</span>
<span class="k">def</span> <span class="nf">reply_msg</span><span class="p">():</span>
<span class="n">Bot</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'USERNAME_'</span><span class="p">],</span> <span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'PASSWORD'</span><span class="p">])</span><span class="o">.</span><span class="n">listen</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">send_msg</span><span class="p">():</span>
<span class="n">schedule</span><span class="o">.</span><span class="n">every</span><span class="p">()</span><span class="o">.</span><span class="n">day</span><span class="o">.</span><span class="n">at</span><span class="p">(</span><span class="s1">'00:00'</span><span class="p">)</span><span class="o">.</span><span class="n">do</span><span class="p">(</span><span class="n">job_that_executes_once</span><span class="p">))</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">schedule</span><span class="o">.</span><span class="n">run_pending</span><span class="p">()</span>
<span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
<span class="n">thread1</span> <span class="o">=</span> <span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">send_msg</span><span class="p">)</span>
<span class="n">thread2</span> <span class="o">=</span> <span class="n">Thread</span><span class="p">(</span><span class="n">target</span><span class="o">=</span><span class="n">reply_msg</span><span class="p">)</span>
<span class="n">thread1</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
<span class="n">thread2</span><span class="o">.</span><span class="n">start</span><span class="p">()</span>
<span class="n">thread1</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
<span class="n">thread2</span><span class="o">.</span><span class="n">join</span><span class="p">()</span>
<span class="k">if</span> <span class="vm">__name__</span> <span class="o">==</span> <span class="s1">'__main__'</span><span class="p">:</span>
<span class="n">main</span><span class="p">()</span>
</code></pre></div>
<p>Bây giờ export username, password rồi chạy thôi. Và đây là thành quả:</p>
<p><img alt="Imgur" src="https://i.imgur.com/VldjbDi.png"></p>
<p>Nếu bạn muốn chạy luôn mà không cần hẹn giờ thì chỉ cần xoá function <code>job_that_executes_once</code> và thay function <code>send_msg</code> bằng:</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">send_msg</span><span class="p">():</span>
<span class="n">Bot</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'USERNAME_'</span><span class="p">],</span><span class="n">os</span><span class="o">.</span><span class="n">environ</span><span class="p">[</span><span class="s1">'PASSWORD'</span><span class="p">])</span><span class="o">.</span><span class="n">do_something</span><span class="p">()</span>
</code></pre></div>
<p>HẾT.</p>
<p>TUNG491 at http://pymi.vn/</p>Dùng .NET Framework với IronPython2018-06-02T00:00:00+07:002018-06-04T00:00:00+07:00htlcnntag:pp.pymi.vn,2018-06-02:/article/ironpython-addreference/<p>Giới thiệu cách thêm các công cụ của .NET Framework vào IronPython.</p><h2>Assembly là gì</h2>
<p><a href="https://msdn.microsoft.com/en-us/library/ms973231.aspx#assenamesp_topic4">Assembly</a> (số nhiều: assemblies) là một file được tạo ra bởi quá trình compile một ứng dụng .NET. Nó có thể có đuôi <code>.dll</code> hoặc <code>.exe</code>. .NET Framework có sẵn rất nhiều assemblies (chính là thành phần <a href="https://msdn.microsoft.com/en-us/library/gg145045.aspx">Class Library trong .NET Framework</a>), cũng tương tự như Python có sẵn rất nhiều <a href="https://docs.python.org/3/library/index.html">standard libraries</a> vậy.</p>
<h2>AddReference .NET Assemblies</h2>
<p>Khi lập trình các ngôn ngữ .NET khác như C# hay VB.NET, dùng Visual Studio, muốn sử dụng các công cụ trong .NET Framework thì bạn phải thêm "Reference" vào project browser. IronPython có 1 module hỗ trợ "Add Reference" vào script là <code>clr</code>. Các methods Add Reference trong IronPython:</p>
<div class="highlight"><pre><span></span><code><span class="c1"># Sử dụng một trong các methods sau</span>
<span class="n">clr</span><span class="o">.</span><span class="n">AddReference</span>
<span class="n">clr</span><span class="o">.</span><span class="n">AddReferenceByName</span>
<span class="n">clr</span><span class="o">.</span><span class="n">AddReferenceByPartialName</span>
<span class="n">clr</span><span class="o">.</span><span class="n">AddReferenceToFile</span>
<span class="n">clr</span><span class="o">.</span><span class="n">AddReferenceToFileAndPath</span>
</code></pre></div>
<p><code>clr.AddReference</code> nhận vào 1 đối tượng <a href="https://msdn.microsoft.com/en-us/library/system.reflection.assembly(v=vs.110).aspx">System.Reflection.Assembly</a>, hoặc <strong>full name</strong> của assembly (vd <code>clr.AddReference("System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")</code>), hoặc <strong>partial name</strong> của assembly (vd <code>clr.AddReference("System.Drawing")</code>). Khi dùng partial name, IronPython sẽ tìm file dll ở trong <a href="https://docs.microsoft.com/en-us/dotnet/framework/app-domains/gac">Global Assembly Cache (GAC)</a>. Mình thường dùng cách thứ 3: truyền vào partial name cho ngắn gọn.
Xem các assemblies đã add ở <code>clr.References</code>:</p>
<div class="highlight"><pre><span></span><code><span class="n">C</span><span class="p">:</span>\<span class="n">Users</span>\<span class="n">HTL</span><span class="o">></span><span class="s2">"c:\Program Files (x86)\IronPython 2.7\ipy.exe"</span>
<span class="n">IronPython</span> <span class="mf">2.7.3</span> <span class="p">(</span><span class="mf">2.7.0.40</span><span class="p">)</span> <span class="n">on</span> <span class="o">.</span><span class="n">NET</span> <span class="mf">4.0.30319.42000</span> <span class="p">(</span><span class="mi">32</span><span class="o">-</span><span class="n">bit</span><span class="p">)</span>
<span class="n">Type</span> <span class="s2">"help"</span><span class="p">,</span> <span class="s2">"copyright"</span><span class="p">,</span> <span class="s2">"credits"</span> <span class="ow">or</span> <span class="s2">"license"</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">clr</span>
<span class="o">>>></span> <span class="n">clr</span><span class="o">.</span><span class="n">AddReference</span><span class="p">(</span><span class="s2">"System.Drawing"</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">clr</span><span class="o">.</span><span class="n">References</span>
<span class="p">(</span><span class="o"><</span><span class="n">mscorlib</span><span class="p">,</span> <span class="n">Version</span><span class="o">=</span><span class="mf">4.0.0.0</span><span class="p">,</span> <span class="n">Culture</span><span class="o">=</span><span class="n">neutral</span><span class="p">,</span> <span class="n">PublicKeyToken</span><span class="o">=</span><span class="n">b77a5c561934e089</span><span class="o">></span><span class="p">,</span>
<span class="o"><</span><span class="n">System</span><span class="p">,</span> <span class="n">Version</span><span class="o">=</span><span class="mf">4.0.0.0</span><span class="p">,</span> <span class="n">Culture</span><span class="o">=</span><span class="n">neutral</span><span class="p">,</span> <span class="n">PublicKeyToken</span><span class="o">=</span><span class="n">b77a5c561934e089</span><span class="o">></span><span class="p">,</span>
<span class="o"><</span><span class="n">IronPython</span><span class="o">.</span><span class="n">SQLite</span><span class="p">,</span> <span class="n">Version</span><span class="o">=</span><span class="mf">2.7.0.40</span><span class="p">,</span> <span class="n">Culture</span><span class="o">=</span><span class="n">neutral</span><span class="p">,</span> <span class="n">PublicKeyToken</span><span class="o">=</span><span class="mi">7</span><span class="n">f709c5b71</span>
<span class="mf">3576e1</span><span class="o">></span><span class="p">,</span>
<span class="o"><</span><span class="n">IronPython</span><span class="o">.</span><span class="n">Wpf</span><span class="p">,</span> <span class="n">Version</span><span class="o">=</span><span class="mf">2.7.0.40</span><span class="p">,</span> <span class="n">Culture</span><span class="o">=</span><span class="n">neutral</span><span class="p">,</span> <span class="n">PublicKeyToken</span><span class="o">=</span><span class="mi">7</span><span class="n">f709c5b713576e1</span><span class="o">></span><span class="p">,</span>
<span class="o"><</span><span class="n">System</span><span class="o">.</span><span class="n">Drawing</span><span class="p">,</span> <span class="n">Version</span><span class="o">=</span><span class="mf">4.0.0.0</span><span class="p">,</span> <span class="n">Culture</span><span class="o">=</span><span class="n">neutral</span><span class="p">,</span> <span class="n">PublicKeyToken</span><span class="o">=</span><span class="n">b03f5f7f11d50a3a</span><span class="o">></span><span class="p">)</span>
<span class="o">>>></span>
</code></pre></div>
<p>Sau khi <code>AddReference</code>, phải <code>import</code> các namespaces có trong assemblies để sử dụng trong IronPython. Cú pháp import như bình thường:</p>
<div class="highlight"><pre><span></span><code><span class="o">>>></span> <span class="kn">from</span> <span class="nn">System</span> <span class="kn">import</span> <span class="n">Drawing</span>
<span class="o">>>></span> <span class="nb">print</span><span class="p">(</span><span class="n">Drawing</span><span class="p">)</span>
<span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Actions</span><span class="o">.</span><span class="n">NamespaceTracker</span><span class="p">:</span><span class="n">System</span><span class="o">.</span><span class="n">Drawing</span>
<span class="o">>>></span>
</code></pre></div>
<p>Thắc mắc hay gặp: Mình muốn sử dụng 1 công cụ trong .NET Framework thì biết tên assembly là gì để <code>AddReference</code>, biết namespace nào để <code>import</code>? Trả lời với từ khóa tìm kiếm: <code>Tên_công_cụ msdn</code> (VD <a href="http://lmgtfy.com/?s=d&q=System+Environment+msdn">System Environment msdn</a>). Dùng phần <strong>Assembly</strong> để <code>AddReference</code>, phần <strong>Namespace</strong> để <code>import</code>.</p>
<p>Khi chạy ipy.exe, IronPython đã AddReference sẵn tới mscorlib.dll và System.dll <a href="http://ironpython.net/documentation/dotnet/dotnet.html#assemblies-loaded-by-default"><sup>[1]</sup></a>. Khi đó chỉ việc <code>import</code> các namespaces có trong 2 assemblies này mà không phải AddRerence.</p>
<h2>AddReference các Assemblies khác</h2>
<p>Ngoài các assemblies có trong .NET Framework, còn có các assemblies khác đi kèm với các phần mềm cài vào Windows, hoặc assemblies được compiled từ chính IronPython. Cú pháp AddReference tương tự như trên, với lưu ý là <code>AddReference</code> khi đó sẽ tìm assemblies trong GAC hoặc <code>sys.path</code>. Nếu không muốn append đường dẫn tới folder chứa file .dll thì có thể dùng <code>AddReferenceToFileAndPath</code>. Phải chạy đúng phiên bản IronPython 32bit/64bit tương ứng với <a href="https://msdn.microsoft.com/en-us/library/hh264221.aspx#Target%20Platform">Target Platform</a> mà file assembly được compiled.</p>
<p>Ví dụ file <code>RevitAPI.dll</code> được compiled với Target Platform là x64 (64-bit), nếu chạy <code>ipy.exe</code> (32-bit) sẽ không <code>AddReference</code> được:</p>
<div class="highlight"><pre><span></span><code><span class="n">C</span><span class="p">:</span>\<span class="n">Users</span>\<span class="n">HTL</span><span class="o">></span><span class="s2">"C:\Program Files (x86)\IronPython 2.7\ipy.exe"</span>
<span class="n">IronPython</span> <span class="mf">2.7.3</span> <span class="p">(</span><span class="mf">2.7.0.40</span><span class="p">)</span> <span class="n">on</span> <span class="o">.</span><span class="n">NET</span> <span class="mf">4.0.30319.42000</span> <span class="p">(</span><span class="mi">32</span><span class="o">-</span><span class="n">bit</span><span class="p">)</span>
<span class="n">Type</span> <span class="s2">"help"</span><span class="p">,</span> <span class="s2">"copyright"</span><span class="p">,</span> <span class="s2">"credits"</span> <span class="ow">or</span> <span class="s2">"license"</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">clr</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">sys</span>
<span class="o">>>></span> <span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">r</span><span class="s1">'C:\Program Files\Autodesk\Revit 2018'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">clr</span><span class="o">.</span><span class="n">AddReference</span><span class="p">(</span><span class="s1">'RevitAPI'</span><span class="p">)</span>
<span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
<span class="n">File</span> <span class="s2">"<stdin>"</span><span class="p">,</span> <span class="n">line</span> <span class="mi">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o"><</span><span class="n">module</span><span class="o">></span>
<span class="ne">IOError</span><span class="p">:</span> <span class="n">System</span><span class="o">.</span><span class="n">IO</span><span class="o">.</span><span class="n">IOException</span><span class="p">:</span> <span class="n">Could</span> <span class="ow">not</span> <span class="n">add</span> <span class="n">reference</span> <span class="n">to</span> <span class="n">assembly</span> <span class="n">RevitAPI</span>
<span class="n">at</span> <span class="n">IronPython</span><span class="o">.</span><span class="n">Runtime</span><span class="o">.</span><span class="n">ClrModule</span><span class="o">.</span><span class="n">AddReference</span><span class="p">(</span><span class="n">CodeContext</span> <span class="n">context</span><span class="p">,</span> <span class="n">String</span> <span class="n">name</span>
<span class="p">)</span>
<span class="n">at</span> <span class="n">IronPython</span><span class="o">.</span><span class="n">Runtime</span><span class="o">.</span><span class="n">ClrModule</span><span class="o">.</span><span class="n">AddReference</span><span class="p">(</span><span class="n">CodeContext</span> <span class="n">context</span><span class="p">,</span> <span class="n">Object</span> <span class="n">refe</span>
<span class="n">rence</span><span class="p">)</span>
<span class="n">at</span> <span class="n">IronPython</span><span class="o">.</span><span class="n">Runtime</span><span class="o">.</span><span class="n">ClrModule</span><span class="o">.</span><span class="n">AddReference</span><span class="p">(</span><span class="n">CodeContext</span> <span class="n">context</span><span class="p">,</span> <span class="n">Object</span><span class="p">[]</span> <span class="n">re</span>
<span class="n">ferences</span><span class="p">)</span>
<span class="n">at</span> <span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">ActionCallInstruction</span><span class="err">`</span><span class="mf">2.</span><span class="n">Run</span><span class="p">(</span><span class="n">InterpretedFra</span>
<span class="n">me</span> <span class="n">frame</span><span class="p">)</span>
<span class="n">at</span> <span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">Run</span><span class="p">(</span><span class="n">InterpretedFrame</span> <span class="n">frame</span><span class="p">)</span>
<span class="n">at</span> <span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">LightLambda</span><span class="o">.</span><span class="n">Run4</span><span class="p">[</span><span class="n">T0</span><span class="p">,</span><span class="n">T1</span><span class="p">,</span><span class="n">T2</span><span class="p">,</span><span class="n">T3</span><span class="p">,</span><span class="n">TRet</span><span class="p">](</span><span class="n">T0</span> <span class="n">arg0</span>
<span class="p">,</span> <span class="n">T1</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">T2</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">T3</span> <span class="n">arg3</span><span class="p">)</span>
<span class="n">at</span> <span class="n">System</span><span class="o">.</span><span class="n">Dynamic</span><span class="o">.</span><span class="n">UpdateDelegates</span><span class="o">.</span><span class="n">UpdateAndExecute3</span><span class="p">[</span><span class="n">T0</span><span class="p">,</span><span class="n">T1</span><span class="p">,</span><span class="n">T2</span><span class="p">,</span><span class="n">TRet</span><span class="p">](</span><span class="n">CallSite</span> <span class="n">s</span>
<span class="n">ite</span><span class="p">,</span> <span class="n">T0</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">T1</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">T2</span> <span class="n">arg2</span><span class="p">)</span>
<span class="n">at</span> <span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">FuncCallInstruction</span><span class="err">`</span><span class="mf">6.</span><span class="n">Run</span><span class="p">(</span><span class="n">InterpretedFrame</span>
<span class="n">frame</span><span class="p">)</span>
<span class="n">at</span> <span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">Run</span><span class="p">(</span><span class="n">InterpretedFrame</span> <span class="n">frame</span><span class="p">)</span>
<span class="n">at</span> <span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">LightLambda</span><span class="o">.</span><span class="n">Run4</span><span class="p">[</span><span class="n">T0</span><span class="p">,</span><span class="n">T1</span><span class="p">,</span><span class="n">T2</span><span class="p">,</span><span class="n">T3</span><span class="p">,</span><span class="n">TRet</span><span class="p">](</span><span class="n">T0</span> <span class="n">arg0</span>
<span class="p">,</span> <span class="n">T1</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">T2</span> <span class="n">arg2</span><span class="p">,</span> <span class="n">T3</span> <span class="n">arg3</span><span class="p">)</span>
<span class="n">at</span> <span class="n">IronPython</span><span class="o">.</span><span class="n">Compiler</span><span class="o">.</span><span class="n">Ast</span><span class="o">.</span><span class="n">CallExpression</span><span class="o">.</span><span class="n">Invoke1Instruction</span><span class="o">.</span><span class="n">Run</span><span class="p">(</span><span class="n">InterpretedF</span>
<span class="n">rame</span> <span class="n">frame</span><span class="p">)</span>
<span class="n">at</span> <span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">Run</span><span class="p">(</span><span class="n">InterpretedFrame</span> <span class="n">frame</span><span class="p">)</span>
<span class="n">at</span> <span class="n">Microsoft</span><span class="o">.</span><span class="n">Scripting</span><span class="o">.</span><span class="n">Interpreter</span><span class="o">.</span><span class="n">LightLambda</span><span class="o">.</span><span class="n">Run2</span><span class="p">[</span><span class="n">T0</span><span class="p">,</span><span class="n">T1</span><span class="p">,</span><span class="n">TRet</span><span class="p">](</span><span class="n">T0</span> <span class="n">arg0</span><span class="p">,</span> <span class="n">T1</span> <span class="n">a</span>
<span class="n">rg1</span><span class="p">)</span>
<span class="n">at</span> <span class="n">IronPython</span><span class="o">.</span><span class="n">Compiler</span><span class="o">.</span><span class="n">PythonScriptCode</span><span class="o">.</span><span class="n">RunWorker</span><span class="p">(</span><span class="n">CodeContext</span> <span class="n">ctx</span><span class="p">)</span>
<span class="n">at</span> <span class="n">IronPython</span><span class="o">.</span><span class="n">Compiler</span><span class="o">.</span><span class="n">PythonScriptCode</span><span class="o">.</span><span class="n">Run</span><span class="p">(</span><span class="n">Scope</span> <span class="n">scope</span><span class="p">)</span>
<span class="n">at</span> <span class="n">IronPython</span><span class="o">.</span><span class="n">Hosting</span><span class="o">.</span><span class="n">PythonCommandLine</span><span class="o">.<></span><span class="n">c__DisplayClass1</span><span class="o">.<</span><span class="n">RunOneInteraction</span>
<span class="o">></span><span class="n">b__0</span><span class="p">()</span>
<span class="o">>>></span>
</code></pre></div>
<p>Mà phải dùng <code>ipy64.exe</code> để <code>AddReference</code>:</p>
<div class="highlight"><pre><span></span><code><span class="n">C</span><span class="p">:</span>\<span class="n">Users</span>\<span class="n">HTL</span><span class="o">></span><span class="s2">"C:\Program Files (x86)\IronPython 2.7\ipy64.exe"</span>
<span class="n">IronPython</span> <span class="mf">2.7.3</span> <span class="p">(</span><span class="mf">2.7.0.40</span><span class="p">)</span> <span class="n">on</span> <span class="o">.</span><span class="n">NET</span> <span class="mf">4.0.30319.42000</span> <span class="p">(</span><span class="mi">64</span><span class="o">-</span><span class="n">bit</span><span class="p">)</span>
<span class="n">Type</span> <span class="s2">"help"</span><span class="p">,</span> <span class="s2">"copyright"</span><span class="p">,</span> <span class="s2">"credits"</span> <span class="ow">or</span> <span class="s2">"license"</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">clr</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">sys</span>
<span class="o">>>></span> <span class="n">sys</span><span class="o">.</span><span class="n">path</span>
<span class="p">[</span><span class="s1">'.'</span><span class="p">,</span> <span class="s1">'C:</span><span class="se">\\</span><span class="s1">Program Files (x86)</span><span class="se">\\</span><span class="s1">IronPython 2.7</span><span class="se">\\</span><span class="s1">Lib'</span><span class="p">,</span>
<span class="s1">'C:</span><span class="se">\\</span><span class="s1">Program Files (x86)</span><span class="se">\\</span><span class="s1">IronPython 2.7</span><span class="se">\\</span><span class="s1">DLLs'</span><span class="p">,</span>
<span class="s1">'C:</span><span class="se">\\</span><span class="s1">Program Files (x86)</span><span class="se">\\</span><span class="s1">IronPython 2.7'</span><span class="p">,</span>
<span class="s1">'C:</span><span class="se">\\</span><span class="s1">Program Files (x86)</span><span class="se">\\</span><span class="s1">IronPython 2.7</span><span class="se">\\</span><span class="s1">lib</span><span class="se">\\</span><span class="s1">site-packages'</span><span class="p">]</span>
<span class="o">>>></span> <span class="n">sys</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="sa">r</span><span class="s1">'C:\Program Files\Autodesk\Revit 2018'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">clr</span><span class="o">.</span><span class="n">AddReference</span><span class="p">(</span><span class="s1">'RevitAPI'</span><span class="p">)</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">Autodesk.Revit.DB</span> <span class="kn">import</span> <span class="n">Wall</span>
<span class="o">>>></span> <span class="n">Wall</span>
<span class="o"><</span><span class="nb">type</span> <span class="s1">'Wall'</span><span class="o">></span>
<span class="o">>>></span>
</code></pre></div>
<h2>AddReference các assemblies được compiled bằng IronPython</h2>
<p>Nếu như bạn chưa biết, IronPython có thể compile python script thành các file assemblies (<code>.dll</code> hoặc <code>.exe</code>) sử dụng công cụ kèm theo là <code>pyc.py</code> (sẽ có bài viết riêng về <strong>Compile python script dùng IronPython</strong>).</p>
<p>Một lưu ý quan trọng khi AddReference các file assemblies này là: <strong>phải dùng đúng phiên bản IronPython tương ứng với phiên bản đã dùng để compile ra file assembly đó thì mới AddReference được</strong>. Ví dụ: file assembly được compiled bằng IronPython 2.7.3 thì khi AddReference ta phải dùng IronPython 2.7.3. Sẽ có ví dụ cụ thể trong bài <strong>Compile python script dùng IronPython</strong>.</p>IronPython2018-05-30T00:00:00+07:002018-05-31T00:00:00+07:00htlcnntag:pp.pymi.vn,2018-05-30:/article/ironpython/<p>Hướng dẫn cài đặt, chạy IronPython trên Windows. Ví dụ cơ bản về cách sử dụng .NET Framework và các thư viện có sẵn với IronPython.</p><h2>IronPython là gì?</h2>
<p>Theo giới thiệu ở <a href="http://ironpython.net/">trang chủ IronPython</a>:</p>
<blockquote>
<p>IronPython is an open-source implementation of the Python programming language which is tightly integrated with the .NET Framework. IronPython can use the .NET Framework and Python libraries, and other .NET languages can use Python code just as easily.</p>
<p>Ironpython là 1 "implementation" mã nguồn mở của ngôn ngữ lập trình Python, tích hợp chặt chẽ với .NET Framework. IronPython có thể sử dụng .NET Framework và các thư viện Python, các ngôn ngữ .NET khác cũng có thể đọc và chạy code Python dễ dàng.</p>
</blockquote>
<h2>Cài đặt IronPython</h2>
<p>Vào trang chủ download bộ cài đặt về (link download ở trang chủ là link github). Thời điểm viết bài, IronPython đang ở <a href="https://github.com/IronLanguages/ironpython2/releases/tag/ipy-2.7.8">phiên bản 2.7.8</a>. IronPython 3 đang trong quá trình phát triển, chưa có bản chính thức.</p>
<h2>Sử dụng</h2>
<ul>
<li>Bật IronPython interpreter bằng cách chạy (bấm đúp - double click) vào file ở đường dẫn: <code>C:\Program Files (x86)\IronPython 2.7\ipy.exe</code> hoặc <code>C:\Program Files (x86)\IronPython 2.7\ipy64.exe</code>. Hoặc bấm nút Start -> gõ cmd -> Enter, sau khi cửa sổ Command Prompt hiện lên thì gõ đường dẫn file: <code>C:\Program Files (x86)\IronPython 2.7\ipy.exe</code> hoặc <code>C:\Program Files (x86)\IronPython 2.7\ipy64.exe</code>:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="n">Microsoft</span> <span class="n">Windows</span> <span class="p">[</span><span class="n">Version</span> <span class="mf">6.1.7601</span><span class="p">]</span>
<span class="n">Copyright</span> <span class="p">(</span><span class="n">c</span><span class="p">)</span> <span class="mi">2009</span> <span class="n">Microsoft</span> <span class="n">Corporation</span><span class="o">.</span> <span class="n">All</span> <span class="n">rights</span> <span class="n">reserved</span><span class="o">.</span>
<span class="n">C</span><span class="p">:</span>\<span class="n">Users</span>\<span class="n">HTL</span><span class="o">></span><span class="s2">"c:\Program Files (x86)\IronPython 2.7\ipy.exe"</span>
<span class="n">IronPython</span> <span class="mf">2.7.3</span> <span class="p">(</span><span class="mf">2.7.0.40</span><span class="p">)</span> <span class="n">on</span> <span class="o">.</span><span class="n">NET</span> <span class="mf">4.0.30319.42000</span> <span class="p">(</span><span class="mi">32</span><span class="o">-</span><span class="n">bit</span><span class="p">)</span>
<span class="n">Type</span> <span class="s2">"help"</span><span class="p">,</span> <span class="s2">"copyright"</span><span class="p">,</span> <span class="s2">"credits"</span> <span class="ow">or</span> <span class="s2">"license"</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
<span class="o">>>></span> <span class="nb">print</span><span class="p">(</span><span class="s2">"Hello from IronPython"</span><span class="p">)</span>
<span class="n">Hello</span> <span class="kn">from</span> <span class="nn">IronPython</span>
</code></pre></div>
<ul>
<li>Chạy 1 python script đã viết sẵn bằng cách gõ vào Command Prompt: <code>C:\Program Files (x86)\IronPython 2.7\ipy.exe đường_dẫn\đến\script.py</code>. Ví dụ nội dung file <code>D:\HTL\Desktop\script.py</code>:</li>
</ul>
<div class="highlight"><pre><span></span><code><span class="nb">print</span><span class="p">(</span><span class="s2">"Hello from inside a Python script"</span><span class="p">)</span>
</code></pre></div>
<div class="highlight"><pre><span></span><code>C:\Users\HTL>"c:\Program Files (x86)\IronPython 2.7\ipy.exe" D:\HTL\Desktop\script.py
Hello from inside a Python script
</code></pre></div>
<h2>Sử dụng .NET Framework và Python libraries</h2>
<p>IronPython có sẵn thư viện <code>clr</code> hỗ trợ load các .NET Assemblies (VD như các file .dll) và sử dụng các công cụ trong đó. IronPython cũng có các libraries kèm theo tương tự như CPython:</p>
<div class="highlight"><pre><span></span><code><span class="n">C</span><span class="p">:</span>\<span class="n">Users</span>\<span class="n">HTL</span><span class="o">></span><span class="s2">"c:\Program Files (x86)\IronPython 2.7\ipy.exe"</span>
<span class="n">IronPython</span> <span class="mf">2.7.3</span> <span class="p">(</span><span class="mf">2.7.0.40</span><span class="p">)</span> <span class="n">on</span> <span class="o">.</span><span class="n">NET</span> <span class="mf">4.0.30319.42000</span> <span class="p">(</span><span class="mi">32</span><span class="o">-</span><span class="n">bit</span><span class="p">)</span>
<span class="n">Type</span> <span class="s2">"help"</span><span class="p">,</span> <span class="s2">"copyright"</span><span class="p">,</span> <span class="s2">"credits"</span> <span class="ow">or</span> <span class="s2">"license"</span> <span class="k">for</span> <span class="n">more</span> <span class="n">information</span><span class="o">.</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">clr</span>
<span class="o">>>></span> <span class="n">clr</span><span class="o">.</span><span class="n">AddReference</span><span class="p">(</span><span class="s1">'System'</span><span class="p">)</span>
<span class="o">>>></span> <span class="kn">from</span> <span class="nn">System</span> <span class="kn">import</span> <span class="n">Environment</span>
<span class="o">>>></span> <span class="nb">print</span><span class="p">(</span><span class="n">Environment</span><span class="o">.</span><span class="n">SystemDirectory</span><span class="p">)</span>
<span class="n">C</span><span class="p">:</span>\<span class="n">Windows</span>\<span class="n">system32</span>
<span class="o">>>></span> <span class="kn">import</span> <span class="nn">math</span>
<span class="o">>>></span> <span class="nb">print</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">pi</span><span class="p">)</span>
<span class="mf">3.14159265359</span>
</code></pre></div>
<h2>Tài liệu IronPython</h2>
<p>Tương tự câu hỏi khi học Python thì dùng tài liệu nào, đối với IronPython thì cũng lấy tài liệu ở trang chủ. IronPython Documentation có 2 phần: phần 1 chính là <a href="https://docs.python.org/2.7/">Python 2.7 documentation</a>, phần 2 là <a href="http://ironpython.net/documentation/dotnet/">hướng dẫn tích hợp .NET với IronPython</a>.</p>
<h2>Kết luận</h2>
<ul>
<li>Học ngôn ngữ Python là có thể sử dụng trên nhiều môi trường, với nhiều implementation khác nhau (CPython, IronPython, Jython...).</li>
<li>IronPython là sự lựa chọn hợp lý khi làm việc với môi trường Windows và các phần mềm chạy trên Windows.</li>
<li>Alternative: Trả lời cho câu hỏi "Mình vẫn muốn dùng CPython và vẫn muốn sử dụng .NET Framework thì làm như thế nào?" -> Tìm hiểu <a href="http://pythonnet.github.io/">pythonnet</a> nhé. Tuy nhiên mình khuyến khích dùng IronPython để làm việc trên Windows và các phần mềm chạy trên Windows.</li>
</ul>Awk siêu tốc2018-05-27T00:00:00+07:002018-05-27T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-05-27:/article/awk/<p>học và dùng luôn trong 20 phút - yêu cầu dùng Ubuntu/OSX hay các hệ điều hành UNIX-like.</p><p>Nếu bạn đã biết 1 scripting language như Python hay Perl việc học AWK sẽ có vẻ hơi
thừa/ hơi ngần ngại. Nhưng AWK - ngôn ngữ lập trình sinh ra từ những năm 1970 luôn
có chỗ dùng, và sức mạnh đáng gờm.</p>
<p>Bài này giới thiệu các khái niệm cơ bản của ngôn ngữ lập trình AWK, một số cách dùng thông dụng, học xong có thể dùng ngay và vô cùng hữu ích với những người xử lý dữ liệu dạng cột/ bảng mà không biết Python.</p>
<p>Các lệnh trong bài này dùng <code>mawk</code> vì nó được biết là nhanh hơn các bản awk khác, nhưng về tính năng cơ bản là giống nhau, người đọc dùng bản nào cũng được, VD gawk</p>
<div class="highlight"><pre><span></span><code>$ whatis awk
awk <span class="o">(</span><span class="m">1</span><span class="o">)</span> - pattern scanning and processing language
</code></pre></div>
<h3>Dùng trong các câu lệnh hàng ngày</h3>
<p>AWK rất thích hợp để nhét vào 1 pipe các câu lệnh UNIX, việc mà Python vốn không sinh ra để làm</p>
<p>Ví dụ: đếm số dòng trong file /etc/passwd</p>
<div class="highlight"><pre><span></span><code>$ wc /etc/passwd
<span class="m">57</span> <span class="m">95</span> <span class="m">3193</span> /etc/passwd
</code></pre></div>
<p>Lệnh <code>wc</code> vốn sinh ra để đếm: dòng, số từ, số ký tự. Bạn có thể viết 1 script Python 5-7 dòng
làm chuyện này, nhưng 1 dòng? Hãy thử và nếu thành công, hãy comment!</p>
<p>Với AWK, làm việc này không khó khăn gì. Hãy bỏ qua nếu không hiểu gì, phần giải thích sẽ theo sau</p>
<div class="highlight"><pre><span></span><code>$ cat /etc/passwd <span class="p">|</span> mawk <span class="s1">'{ words += NF; chars += length($0) + 1;} END { print NR, words, chars}'</span>
<span class="m">57</span> <span class="m">95</span> <span class="m">3193</span>
</code></pre></div>
<p>Một pipe (<code>cmd1 | cmd2 | cmd3</code>) thường bắt đầu bằng <code>cat file</code>, mặc dù nó không phải cách làm tốt (tốn thêm 1 câu lệnh cat - khi mà hầu hết các câu lệnh đều hỗ trợ đầu vào là 1 file), nhưng tiện, nên ta cứ bắt đầu với cat, khi nào cần tối ưu ta sẽ bỏ nó đi. Ở đây, <code>mawk</code> có thể nhận file để xử lý, ta viết lại:</p>
<div class="highlight"><pre><span></span><code>$ mawk <span class="s1">'{ words += NF; chars += length($0) + 1;} END { print NR, words, chars}'</span> /etc/passwd
<span class="m">57</span> <span class="m">95</span> <span class="m">3193</span>
</code></pre></div>
<p>Một ví dụ khác</p>
<div class="highlight"><pre><span></span><code>$ head /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
</code></pre></div>
<p>Một yêu cầu quái dị có thể xuất hiện như: hãy tính tổng các số ở cột số 3, với các câu lệnh CLI truyền thống, ta làm như sau</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">echo</span> <span class="k">$(</span>cat /etc/passwd <span class="p">|</span> cut -d: -f3 <span class="p">|</span> xargs <span class="nb">printf</span> <span class="s1">'%s +'</span><span class="k">)</span> <span class="m">0</span> <span class="p">|</span> bc
<span class="m">75624</span>
</code></pre></div>
<p><code>cut</code> giúp lấy cột thứ 3, phân cách bằng dấu <code>:</code>, dùng <code>printf</code> để nối các số lại bằng dấu <code>+</code>, vì thừa 1 dấu <code>+</code> ở đuôi nên ta <code>echo</code> thêm số 0 để cho hợp lệ rồi đưa vào lệnh <code>bc</code> để tính.</p>
<div class="highlight"><pre><span></span><code>$ mawk -F: <span class="s1">'{sum += $3} END { print sum }'</span> /etc/passwd
<span class="m">75624</span>
</code></pre></div>
<p>Tương tự <code>cut</code>, ta dùng <code>-F:</code> để chỉ ra sẽ dùng <code>:</code> để phân cách cột, lấy cột số 3 <code>$3</code>, cộng
lần lượt các giá trị vào biến <code>sum</code> - khi ta không khai báo, AWK mặc định đó là số 0 nếu thực hiện phép toán với số. Làm như vậy với mỗi dòng (gọi là <em>record</em> trong AWK):</p>
<div class="highlight"><pre><span></span><code>{ sum += $3 }
</code></pre></div>
<p>Và khi hết các dòng (đánh dấu bằng END), ta print ra kết quả</p>
<div class="highlight"><pre><span></span><code><span class="kr">END</span> <span class="p">{</span> <span class="n">print</span> <span class="n">sum</span> <span class="p">}</span>
</code></pre></div>
<p>Khó có thể đánh bại sự ngắn gọn, sạch sẽ này.</p>
<h2>Record và field</h2>
<p>AWK đọc text vào, mặc định sẽ cắt tại các dấu xuống dòng, tạo thành các record,
sau đó mặc định cắt tại dấu space (khoảng trắng), tạo thành các field.
Ta hoàn toàn có thể chỉ định AWK cắt ở ký tự khác.</p>
<h2>Cấu tạo một chương trình AWK</h2>
<p>Một chương trình AWK cấu tạo bởi các câu lệnh (statements) với cú pháp:</p>
<div class="highlight"><pre><span></span><code> pattern { action statements }
</code></pre></div>
<p>Chỉ có vậy.
Khi viết vào file thì viết mỗi câu lệnh 1 dòng, nhưng hoàn toàn có thể viết trên 1 dòng.</p>
<h3>partern</h3>
<p>Có nhiều partern, nhưng 3 partern phổ biến và hữu ích nhất là:</p>
<ul>
<li>BEGIN: theo sau là hành động / câu lệnh khi chương trình bắt đầu</li>
<li>END: theo sau là câu lệnh được chạy khi chương trình kết thúc</li>
<li>KHÔNG GHI PATTERN: thực hiện câu lệnh này cho mỗi record</li>
</ul>
<p>Ví dụ</p>
<div class="highlight"><pre><span></span><code><span class="s">'{sum += $3} END { print sum }'</span>
</code></pre></div>
<p>Ví dụ sau không có BEGIN, với mỗi record ta sẽ cộng dồn, và khi đọc hết các record, ta print ra tổng. Chú ý: dấu <code>\</code> ở cuối dòng là cú pháp trong shell cho phép viết 1 dòng thành nhiều dòng, khi chạy AWK chỉ xem toàn bộ chương trình là 1 dòng.</p>
<div class="highlight"><pre><span></span><code><span class="o">$</span> <span class="nx">echo</span> <span class="s1">'pymi\npython\nfamilug'</span> <span class="o">|</span> <span class="nx">mawk</span> <span class="s1">'\</span>
<span class="s1"> BEGIN { print "the begin" } \</span>
<span class="s1"> { printf "got line: %s has length %d\n", $0, length($0) }\</span>
<span class="s1"> { print "done process line ", NR }\</span>
<span class="s1"> END {print "DONE"}'</span>
<span class="nx">the</span> <span class="nx">begin</span>
<span class="nx">got</span> <span class="nx">line</span><span class="err">:</span> <span class="nx">pymi</span> <span class="nx">has</span> <span class="kr">length</span> <span class="mi">4</span>
<span class="nx">done</span> <span class="nx">process</span> <span class="nx">line</span> <span class="mi">1</span>
<span class="nx">got</span> <span class="nx">line</span><span class="err">:</span> <span class="nx">python</span> <span class="nx">has</span> <span class="kr">length</span> <span class="mi">6</span>
<span class="nx">done</span> <span class="nx">process</span> <span class="nx">line</span> <span class="mi">2</span>
<span class="nx">got</span> <span class="nx">line</span><span class="err">:</span> <span class="nx">familug</span> <span class="nx">has</span> <span class="kr">length</span> <span class="mi">7</span>
<span class="nx">done</span> <span class="nx">process</span> <span class="nx">line</span> <span class="mi">3</span>
<span class="nx">DONE</span>
</code></pre></div>
<h2>parttern tìm kiếm</h2>
<p>parttern này giúp tìm kiếm các record có chứa một regular expression (hoặc string cố định) và xử lý record đó.</p>
<p>Đếm số dòng chứa từ <code>print</code>:</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">echo</span> -e <span class="s2">"printf print \nprintf"</span>
<span class="nb">printf</span> print
<span class="nb">printf</span>
$ <span class="nb">echo</span> -e <span class="s2">"printf print \nprintf"</span> <span class="p">|</span> grep -c print
<span class="m">2</span>
$ <span class="nb">echo</span> -e <span class="s2">"printf print \nprintf"</span> <span class="p">|</span> mawk <span class="s1">'/print/ { count += 1 } END { print count }'</span>
<span class="m">2</span>
</code></pre></div>
<h2>print</h2>
<p><code>print</code> và <code>printf</code> là 2 output statements (câu lệnh xử lý đầu ra), không phải function nên
không sử dụng cú pháp gọi function <code>print(thing)</code></p>
<div class="highlight"><pre><span></span><code>$ mawk <span class="s1">'BEGIN { print "hello" }'</span>
hello
</code></pre></div>
<h2>variable và field variable</h2>
<p>Mỗi record chứa nhiều field, hãy tưởng tượng như 1 dòng spreadsheet/excel có nhiều ô ứng với các cột.
Các variable có sẵn:</p>
<ul>
<li><code>$0</code> - toàn bộ 1 record (dòng) - chú ý dấu $ dùng để truy cập giá trị của field</li>
<li><code>$1</code> - field 1 (hay có thể nghĩ là cột số 1)</li>
<li><code>$2</code> - field 2</li>
<li>...</li>
<li><code>$(NF - 1)</code> - field trước cuối cùng</li>
<li><code>$NF</code> - field cuối cùng</li>
<li>NF - số field</li>
<li>NR - thứ tự của record, tăng dần sau khi xử lý xong mỗi record (số dòng hiện tại)</li>
</ul>
<p>Khi truy cập biến, không sử dụng thêm bất cứ ký tự nào (khác với bash, bash phải thêm $varname).</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">echo</span> <span class="s1">'1, Le Van Xe, 27, Ha Noi, 0990090090'</span> <span class="p">|</span> mawk -F, <span class="s1">'{ print $NF}'</span>
<span class="m">0990090090</span>
$ <span class="nb">echo</span> <span class="s1">'1, Le Van Xe, 27, Ha Noi, 0990090090'</span> <span class="p">|</span> mawk -F, <span class="s1">'{ print $0}'</span>
<span class="m">1</span>, Le Van Xe, <span class="m">27</span>, Ha Noi, <span class="m">0990090090</span>
$ <span class="nb">echo</span> <span class="s1">'1, Le Van Xe, 27, Ha Noi, 0990090090'</span> <span class="p">|</span> mawk -F, <span class="s1">'{ print NF }'</span>
<span class="m">5</span>
</code></pre></div>
<p>Chỉ cần nắm rõ tên các variable có sẵn này, ta đã có một tool cực mạnh để xử lý dữ liệu dạng bảng như Excel/CSV/SQL.</p>
<h2>Các kiểu dữ liệu</h2>
<p>AWK có string, số (chia làm float và integer), và array.
Ta không bàn tới array ở đây, vì nếu cần lập trình gì phức tạp hơn một câu lệnh AWK đơn giản, ta có thể sử dụng Python cho sạch sẽ, gọn gàng, tiện lợi.</p>
<h2>Control flow</h2>
<p>Là ngôn ngữ lập trình đầy đủ, AWK có if/else/for/while như các ngôn ngữ lập trình "giống C" khác. Ví dụ đếm số chẵn và số lẻ</p>
<div class="highlight"><pre><span></span><code>$ <span class="nb">echo</span> -e <span class="s1">'1\n2\n3\n4\n20\n30'</span> <span class="p">|</span> mawk <span class="s1">'\</span>
<span class="s1">{ if ($1 % 2 == 0) \</span>
<span class="s1"> { chan += 1 } \</span>
<span class="s1"> else { le += 1 } \</span>
<span class="s1">} \</span>
<span class="s1">END { print chan, le }'</span>
<span class="m">5</span> <span class="m">1</span>
</code></pre></div>
<h2>Function</h2>
<p>AWK có sẵn các funtion xử lý string, số thì đủ các phép toán, các function sin cos ..., người dùng cũng có thể định nghĩa function của họ. Xem thêm ở <a href="https://www.gnu.org/software/gawk/manual/gawk.html#Functions">đây</a>.</p>
<h3>Bonus - giải bài projecteuler 1</h3>
<p><a href="https://projecteuler.net/problem=1">Tính tổng các số nhỏ hơn 1000 chia hết cho 3 hoặc 5</a></p>
<div class="highlight"><pre><span></span><code>$ seq <span class="m">1</span> <span class="m">999</span> <span class="p">|</span> mawk <span class="s1">'{ if ($1 % 3 == 0 || $i % 5 == 0) sum += $1 } END { print sum }'</span>
<span class="m">233168</span>
</code></pre></div>
<h2>Tham khảo</h2>
<ul>
<li><a href="http://invisible-island.net/mawk/manpage/mawk.txt">mawk homepage manpage</a></li>
<li><a href="http://manpages.ubuntu.com/manpages/xenial/en/man1/mawk.1.html">ubuntu 16.04 man 1 mawk</a></li>
<li><a href="https://www.gnu.org/software/gawk/manual/gawk.html#Top">GNU awk</a></li>
<li><a href="https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html">AWK on big data</a></li>
</ul>
<p>Hết</p>
<p>HVN at <a href="http://pymi.vn">http://pymi.vn</a> and <a href="https://www.familug.org">https://www.familug.org</a>.</p>In ra màn hình số 1 - ngôn ngữ nào nhẹ nhất?2018-05-27T00:00:00+07:002018-05-27T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-05-27:/article/dynamic/<p>dash, bash, awk, perl, ruby, nodejs, python, elixir, guile - tất cả lên thớt!</p><p>Nếu chỉ cần in ra mà hình số 1, ngôn ngữ lập trình nào sẽ chạy tốn ít RAM nhất?</p>
<p>Thí nghiệm sau sẽ cho ta thấy các chương trình cần bao nhiêu RAM để chạy và in
ra số 1, kết thúc dòng bằng một dấu xuống dòng (newline <code>\n</code>).</p>
<p>Ta dùng chương trình <code>/usr/bin/time</code> để có cả thông số về RAM (<code>RSS</code>) thay vì chỉ gõ time - lệnh builtin của bash.</p>
<h2>bash4.3</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time bash -c <span class="s1">'echo 1'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 3064maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+136minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>dash</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time /bin/dash -c <span class="s1">'echo 1'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed ?%CPU <span class="o">(</span>0avgtext+0avgdata 1492maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+67minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>mawk</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time mawk <span class="s1">'BEGIN {print 1}'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 1932maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+86minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>gawk 4.1.3</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time gawk <span class="s1">'BEGIN {print 1}'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">42</span>%CPU <span class="o">(</span>0avgtext+0avgdata 3632maxresident<span class="o">)</span>k
552inputs+0outputs <span class="o">(</span>2major+173minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>Python3.5.2</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time python3 -c <span class="s1">'print(1)'</span>
<span class="m">1</span>
<span class="m">0</span>.03user <span class="m">0</span>.01system <span class="m">0</span>:00.05elapsed <span class="m">98</span>%CPU <span class="o">(</span>0avgtext+0avgdata 9292maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+1100minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>Python2.7.12</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time python2 -c <span class="s1">'print(1)'</span>
<span class="m">1</span>
<span class="m">0</span>.01user <span class="m">0</span>.00system <span class="m">0</span>:00.01elapsed <span class="m">94</span>%CPU <span class="o">(</span>0avgtext+0avgdata 6640maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+819minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>Ruby 2.3.1</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time ruby -e <span class="s1">'puts 1'</span>
<span class="m">1</span>
<span class="m">0</span>.03user <span class="m">0</span>.01system <span class="m">0</span>:00.04elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 10336maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+1595minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>Perl v5.22.1</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time perl -e <span class="s1">'print "1\n"'</span>
<span class="m">1</span>
<span class="m">0</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 4144maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+176minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>NodeJS 6.11.2</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time node -p <span class="s1">'1'</span>
<span class="m">1</span>
<span class="m">0</span>.09user <span class="m">0</span>.00system <span class="m">0</span>:00.10elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 24080maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+2920minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>Elixir 1.6.3</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time elixir -e <span class="s1">'IO.puts 1'</span>
<span class="m">1</span>
<span class="m">0</span>.17user <span class="m">0</span>.04system <span class="m">0</span>:00.16elapsed <span class="m">137</span>%CPU <span class="o">(</span>0avgtext+0avgdata 29268maxresident<span class="o">)</span>k
16inputs+0outputs <span class="o">(</span>1major+7837minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>Guile 2.0.11</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time guile -c <span class="s1">'(display 1) (newline)'</span>
<span class="m">1</span>
<span class="m">0</span>.02user <span class="m">0</span>.00system <span class="m">0</span>:00.01elapsed <span class="m">115</span>%CPU <span class="o">(</span>0avgtext+0avgdata 7720maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+976minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>printf 8.25 - chương trình viết bằng C</h2>
<div class="highlight"><pre><span></span><code>$ /usr/bin/time <span class="nb">printf</span> <span class="s1">'1'</span>
<span class="m">10</span>.00user <span class="m">0</span>.00system <span class="m">0</span>:00.00elapsed <span class="m">100</span>%CPU <span class="o">(</span>0avgtext+0avgdata 1844maxresident<span class="o">)</span>k
0inputs+0outputs <span class="o">(</span>0major+72minor<span class="o">)</span>pagefaults 0swaps
</code></pre></div>
<h2>Làm lại toàn bộ bằng Python</h2>
<p>Tạo một dictionary, chứa các tên ngôn ngữ - kèm với câu lệnh sẽ dùng để thử nghiệm</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">shlex</span>
<span class="kn">import</span> <span class="nn">subprocess</span>
<span class="n">programs</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'bash'</span><span class="p">:</span> <span class="s1">'bash -c "echo 1"'</span><span class="p">,</span>
<span class="s1">'dash'</span><span class="p">:</span> <span class="s1">'dash -c "echo 1"'</span><span class="p">,</span>
<span class="s1">'mawk'</span><span class="p">:</span> <span class="s2">"mawk 'BEGIN {print 1}'"</span><span class="p">,</span>
<span class="s1">'gawk'</span><span class="p">:</span> <span class="s2">"gawk 'BEGIN {print 1}'"</span><span class="p">,</span>
<span class="s1">'python2.7'</span><span class="p">:</span> <span class="s1">'python2 -c "print(1)"'</span><span class="p">,</span>
<span class="s1">'python3.5'</span><span class="p">:</span> <span class="s1">'python3 -c "print(1)"'</span><span class="p">,</span>
<span class="s1">'ruby2.3'</span><span class="p">:</span> <span class="s1">'ruby -e "puts 1"'</span><span class="p">,</span>
<span class="s1">'node6'</span><span class="p">:</span> <span class="s1">'node -p 1'</span><span class="p">,</span>
<span class="s1">'perl5'</span><span class="p">:</span> <span class="s2">"perl -e </span><span class="se">\"</span><span class="s2">print '1</span><span class="se">\n</span><span class="s2">'</span><span class="se">\"</span><span class="s2">"</span><span class="p">,</span>
<span class="s1">'elixir'</span><span class="p">:</span> <span class="s1">'elixir -e "IO.puts 1"'</span><span class="p">,</span>
<span class="s1">'guile'</span><span class="p">:</span> <span class="s1">'guile -c "(display 1) (newline)"'</span><span class="p">,</span>
<span class="s1">'printf'</span><span class="p">:</span> <span class="s1">'printf "1</span><span class="se">\n</span><span class="s1">"'</span>
<span class="p">}</span>
<span class="kn">import</span> <span class="nn">re</span>
<span class="k">def</span> <span class="nf">get_rss_from_stderr</span><span class="p">(</span><span class="n">stderr</span><span class="p">):</span>
<span class="n">match</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">compile</span><span class="p">(</span><span class="s1">'(?P<rss>\d+)maxresident'</span><span class="p">)</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="n">err</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">))</span>
<span class="k">return</span> <span class="nb">int</span><span class="p">(</span><span class="n">match</span><span class="o">.</span><span class="n">group</span><span class="p">(</span><span class="s1">'rss'</span><span class="p">))</span>
<span class="kn">import</span> <span class="nn">pandas</span> <span class="k">as</span> <span class="nn">pd</span>
<span class="n">languages</span> <span class="o">=</span> <span class="p">[]</span>
<span class="n">rsses</span> <span class="o">=</span> <span class="p">[]</span>
<span class="k">for</span> <span class="n">language</span><span class="p">,</span> <span class="n">cmd</span> <span class="ow">in</span> <span class="n">programs</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="n">cmd</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'/usr/bin/time'</span><span class="p">]</span> <span class="o">+</span> <span class="n">shlex</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span>
<span class="n">process</span> <span class="o">=</span> <span class="n">subprocess</span><span class="o">.</span><span class="n">Popen</span><span class="p">(</span><span class="n">cmd</span><span class="p">,</span> <span class="n">stdout</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">,</span> <span class="n">stderr</span><span class="o">=</span><span class="n">subprocess</span><span class="o">.</span><span class="n">PIPE</span><span class="p">)</span>
<span class="n">out</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">process</span><span class="o">.</span><span class="n">communicate</span><span class="p">()</span>
<span class="k">assert</span> <span class="n">out</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="s1">'utf-8'</span><span class="p">)</span> <span class="o">==</span> <span class="s1">'1</span><span class="se">\n</span><span class="s1">'</span>
<span class="n">rss</span> <span class="o">=</span> <span class="n">get_rss_from_stderr</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">language</span><span class="p">,</span> <span class="n">rss</span><span class="p">)</span>
<span class="n">languages</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">language</span><span class="p">)</span>
<span class="n">rsses</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">rss</span><span class="p">)</span>
<span class="n">df</span> <span class="o">=</span> <span class="n">pd</span><span class="o">.</span><span class="n">DataFrame</span><span class="p">(</span><span class="n">index</span><span class="o">=</span><span class="n">languages</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">rsses</span><span class="p">,</span> <span class="n">columns</span><span class="o">=</span><span class="p">[</span><span class="s1">'RSS'</span><span class="p">])</span>
<span class="n">df_sorted_rss</span> <span class="o">=</span> <span class="n">df</span><span class="o">.</span><span class="n">sort_values</span><span class="p">(</span><span class="n">by</span><span class="o">=</span><span class="s1">'RSS'</span><span class="p">)</span>
<span class="c1"># run in Jupyter</span>
<span class="kn">from</span> <span class="nn">matplotlib</span> <span class="kn">import</span> <span class="n">pyplot</span> <span class="k">as</span> <span class="n">plt</span>
<span class="kn">import</span> <span class="nn">matplotlib</span>
<span class="n">matplotlib</span><span class="o">.</span><span class="n">style</span><span class="o">.</span><span class="n">use</span><span class="p">(</span><span class="s1">'fivethirtyeight'</span><span class="p">)</span>
<span class="o">%</span><span class="n">matplotlib</span> <span class="n">inline</span>
<span class="n">df_sorted_rss</span><span class="o">.</span><span class="n">plot</span><span class="o">.</span><span class="n">bar</span><span class="p">(</span><span class="n">title</span><span class="o">=</span><span class="s1">'RSS size in byte when print 1'</span><span class="p">);</span>
</code></pre></div>
<p><img alt="Dynamic typing languages" src="http://pp.pymi.vn/images/dynamic.png"></p>
<h2>Kết luận</h2>
<p>Khoan vội kết luận rằng cả thế giới nên chuyển hết về dùng dash cho đỡ tốn RAM hay xa lánh Elixir vì nó chạy tới 30 MB RAM để in số 1 ra màn hình.</p>
<p>Sử dụng đúng công cụ, cho đúng vấn đề thích hợp mới là điều quan trọng.</p>
<ul>
<li>dash được viết ra để dùng làm <code>shell (sh)</code> cho Ubuntu, nó cần nhẹ, dù trả giá bằng việc không nhiều tính năng, và chỉ là một shell scripting language</li>
<li><a href="http://www.familug.org/search/label/bash">bash</a> là shell được dùng phổ biến nhất thế giới</li>
<li>awk là ngôn ngữ lập trình đã gắn liền với lịch sử của thế giới UNIX, rất tiện để tính toán dữ liệu cột / hàng. Với AWK cùng các CLI tool truyền thống, người ta có thể <a href="https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html">giải quyết bài toán "big data" nhanh gấp 200 lần dùng Hadoop (EMR)</a></li>
<li>mawk nhanh hơn gawk</li>
<li>perl5 luôn có chỗ đứng của nó, nó vẫn ở trên máy Ubuntu dù chưa bao giờ mình chủ ý cài</li>
<li><a href="http://www.familug.org/search/label/guile">guile</a> là extension language chính thức được lựa chọn bởi GNU, nó chính là ngôn ngữ scheme được mang vào ứng dụng thực tế</li>
<li><a href="http://pymi.vn/">Python</a> - ngôn ngữ dễ đọc, phổ biến nhất thế giới, đã đẩy ngành tính toán khoa học từ bộ môn trong phòng thí nghiệm trở thành ngành hot nhất trái đất. PS: Python khi bật lên đã dùng ~ 8MB RAM.</li>
<li>Ruby còn dùng nhiều RAM hơn nữa, dù về mặt tính năng thường được so ngang với Python, nhưng mặt ứng dụng vào khoa học thì là vực so với trời Python.</li>
<li>NodeJS là một JavaScript engine chuyển biến cả ngành web, cũng chưa rõ sao nó ngốn gần 25MB để bật lên, có khi đó vốn là bản chất của JavaScript.</li>
<li><a href="http://elixir.pymi.vn/">Elixir</a> không chỉ bật lên một interpreter như bash, mà đó là cả hệ thống phân tán - chạy máy ảo BEAM của Erlang, nên con số 30MB thậm chí còn là hơi ... ít.</li>
</ul>
<p>Bài này không xét các ngôn ngữ mà phải viết thành file, compile mới chạy, vì tác giả quá lười.</p>
<p>PS: học hết đống trên ở <a href="http://www.familug.org/2016/12/free-ebook.html">đây</a></p>
<p>Hết.</p>
<p>HVN at <a href="https://pymi.vn">https://pymi.vn</a> and https://www.familug.org</p>Tôi học lập trình không phải vì lương 4000 USD2018-05-20T00:00:00+07:002018-05-20T00:00:00+07:00tung491tag:pp.pymi.vn,2018-05-20:/article/toi-hoc-lap-trinh-khong-phai-vi-luong-4000-usd/<p>Không ít học sinh phổ thông từng thắc mắc học môn lập trình để làm gì?
Có phải vì mức lương 4000 USD của sinh viên mới ra trường mà nhiều người hay
nhắc đến khi nói về lập trình viên.</p>
<p>Hôm nay mình đọc một bài trên Facebook nói …</p><p>Không ít học sinh phổ thông từng thắc mắc học môn lập trình để làm gì?
Có phải vì mức lương 4000 USD của sinh viên mới ra trường mà nhiều người hay
nhắc đến khi nói về lập trình viên.</p>
<p>Hôm nay mình đọc một bài trên Facebook nói về lí do <code>Tại sao bạn học lập trình?</code>,
trong đó có 1 lí do được nhắc đi nhắc lại rất nhiều
đó là có một tương lai tươi sáng với mức lương khởi điểm khi ra trường 4000 USD. Bài này nói lên quan điểm của mình về
việc học lập trình để làm gì.</p>
<p><img alt="4000USD" src="https://cdn-images-1.medium.com/max/800/1*MNkBMrAinvq65wLx9y2oIg.jpeg"></p>
<p>Mình cũng giống các bạn trong clip trên, đều là học sinh cấp 3, đều có không ít thì nhiều kiến thức về lập trình nhưng mình thấy các bạn đang có phần ảo tưởng về ngành lập trình. Mình tiếp xúc với không ít lập trình viên chuyên nghiệp, nên không còn lạ gì những nỗi khổ của ngành này: trĩ lòi, mắt mờ, đau lưng, cột sống, stress, deadline, đau dạ dày, trầm cảm... Những người ngoài chỉ nhìn vào họ những lúc họ khoe lương nhưng đâu có nhìn thấy lúc họ làm việc. Trên các loại báo xàm ke của Việt Nam và các bà hàng xóm, nghề <code>lập trình</code> thường xuyên được nhắc đến trong các chủ đề như: "những công việc lương cao nhất", "những công việc không sợ thiếu việc làm lương cao trong 5 năm tới", blabla ... ; nhưng sự thực có phải vậy không ? Công việc lập trình cũng chỉ giống bao nhiêu ngành nghề khác là bán sức lao động và giá trị của bản thân cho công ty và xã hội thôi.</p>
<p>Bố mình từng nói "Không có ngành nghề dễ kiếm việc hơn hay lương cao hơn ngành nghề nào.
Tất cả công việc mày được trả lương là người ta trả cho giá trị và khả năng của mày mang lại chứ không phải trả cho cái ngành nghề của mày hay cái trường mày học."</p>
<p>Lương 4000 USD/tháng với lập trình không phải là không có nhưng những nguời như thế thuộc loại <strong>hiếm</strong> và thường có kinh nghiệm 5–7 năm đi làm. Còn về lương một cử nhân Bách Khoa ra trường thì "4000 USD/năm tương đương khoảng 7.5 triệu đồng/tháng thì hợp lý, và cũng là cao hơn mặt bằng xã hội", "xạo chó", "ra trường 4k có cái củ cải" ... là những câu trả lời mình nhận được khi gửi bức ảnh trên và hỏi những người trong nghề và có có cả những người với mức lương >= 4000 USD.</p>
<p>Quay về vấn đề chính thôi, mình học lập trình để làm gì? Đơn giản thôi, mình học để hiểu về cách của mọi thứ trên Internet đang hoại động và để nó là công cụ cho mình để thực hiện được những gì mình muốn: Làm một trang web, vẽ bản đồ trà sữa, viết một con chatbot, blabla … Với mình, ngôn ngữ trong lập trình cũng giống như Tiếng Anh nó giúp bạn giao tiếp với các lập trình viên khác và thế giới.</p>
<p>Trong cuộc sống, chúng ta không khó để thấy những sự thật về những con người thành công trong nhiều lĩnh vực nhưng những lĩnh vực đó không hề <code>hot</code> hay được truyền thống tung hô ví dụ: nuôi lợn, trồng rau, ... vì đơn giản họ tìm thấy được đam mê của mình với nghề và tiềm năng của nghề. Do đó chúng ta thấy được sự thành công đến từ bản thân bạn chứ không phải ngành nghề của bạn</p>
<p>Tóm lại, đừng thần thánh hóa <strong>lập trình viên</strong>.</p>
<p>HẾT</p>
<p>TUNG491 at https://pymi.vn.</p>Requests + bs4 <= Requests-HTML2018-05-09T00:00:00+07:002018-05-09T00:00:00+07:00tung491tag:pp.pymi.vn,2018-05-09:/article/requests-bs4-requests-html/<h2>Giới thiệu</h2>
<p><a href="http://html.python-requests.org/">Requests-HTML</a> được viết bởi Kenneth Reitz - tác giả của <code>requests</code> nổi tiếng, với mục đích cao cả là thay thế combo huyền thoại (với pymier) requests + bs4. Trên trang chủ, requests-html tự mô tả như sau:</p>
<blockquote>
<p>This library intends to make parsing HTML (e.g. scraping the web …</p></blockquote><h2>Giới thiệu</h2>
<p><a href="http://html.python-requests.org/">Requests-HTML</a> được viết bởi Kenneth Reitz - tác giả của <code>requests</code> nổi tiếng, với mục đích cao cả là thay thế combo huyền thoại (với pymier) requests + bs4. Trên trang chủ, requests-html tự mô tả như sau:</p>
<blockquote>
<p>This library intends to make parsing HTML (e.g. scraping the web) as simple and intuitive as possible</p>
</blockquote>
<p>và đặc biệt chỉ hỗ trợ python3.</p>
<h2>Các tính năng nổi bật của requests-html</h2>
<ul>
<li>Hỗ trợ đầy đủ JavaScript</li>
<li>Hỗ trợ chọn thành phần của trang bằng CSS, Xpath</li>
<li>Tự động theo chuyển trang</li>
</ul>
<h2>Demo vài tính năng nổi bật</h2>
<p>Cách cài đặt và sử dụng cơ bản của requests-html đã được tác giả nói rất rõ ở trang chủ do đó mính sẽ không nói lại nữa.</p>
<h3>Render</h3>
<p>Nhặt một đoạn văn bản mà đã được render bởi JavaScript:</p>
<div class="highlight"><pre><span></span><code><span class="n">In</span> <span class="p">[</span><span class="mi">4</span><span class="p">]:</span> <span class="n">r</span> <span class="o">=</span> <span class="n">session</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'http://www.familug.org/'</span><span class="p">)</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">5</span><span class="p">]:</span> <span class="n">r</span><span class="o">.</span><span class="n">html</span><span class="o">.</span><span class="n">render</span><span class="p">()</span>
<span class="p">[</span><span class="n">W</span><span class="p">:</span><span class="n">pyppeteer</span><span class="o">.</span><span class="n">chromium_downloader</span><span class="p">]</span> <span class="n">start</span> <span class="n">chromium</span> <span class="n">download</span><span class="o">.</span>
<span class="n">Download</span> <span class="n">may</span> <span class="n">take</span> <span class="n">a</span> <span class="n">few</span> <span class="n">minutes</span><span class="o">.</span>
<span class="p">[</span><span class="n">W</span><span class="p">:</span><span class="n">pyppeteer</span><span class="o">.</span><span class="n">chromium_downloader</span><span class="p">]</span> <span class="n">chromium</span> <span class="n">download</span> <span class="n">done</span><span class="o">.</span>
<span class="p">[</span><span class="n">W</span><span class="p">:</span><span class="n">pyppeteer</span><span class="o">.</span><span class="n">chromium_downloader</span><span class="p">]</span> <span class="n">chromium</span> <span class="n">extracted</span> <span class="n">to</span><span class="p">:</span> <span class="o">/</span><span class="n">home</span><span class="o">/</span><span class="n">dosontung007</span><span class="o">/.</span><span class="n">pyppeteer</span><span class="o">/</span><span class="n">local</span><span class="o">-</span><span class="n">chromium</span><span class="o">/</span><span class="mi">543305</span>
<span class="n">In</span> <span class="p">[</span><span class="mi">6</span><span class="p">]:</span> <span class="n">r</span><span class="o">.</span><span class="n">html</span><span class="o">.</span><span class="n">search</span><span class="p">(</span><span class="s1">'UEFI vs BIOS - tạo USB boot cài Windows </span><span class="si">{something}</span><span class="s1"> từ Ubuntu'</span><span class="p">)[</span><span class="s1">'something'</span><span class="p">]</span>
<span class="n">Out</span><span class="p">[</span><span class="mi">6</span><span class="p">]:</span> <span class="s1">'10'</span>
</code></pre></div>
<p>Khi bạn chạy render() lần đầu, method sẽ tải về <code>Chromium headless</code> về thư mục <code>$HOME</code>.</p>
<h3>XPath Selector</h3>
<p>Requests-html hỗ trợ Xpath query như Scrapy</p>
<div class="highlight"><pre><span></span><code><span class="ow">In</span><span class="w"> </span><span class="o">[</span><span class="n">25</span><span class="o">]</span><span class="err">:</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">html</span><span class="p">.</span><span class="n">xpath</span><span class="p">(</span><span class="s1">'//h3//a'</span><span class="p">)</span><span class="w"></span>
<span class="k">Out</span><span class="o">[</span><span class="n">25</span><span class="o">]</span><span class="err">:</span><span class="w"></span>
<span class="o">[</span><span class="n"><Element 'a' href='http://www.familug.org/2018/04/uefi-vs-bios-tao-usb-boot-cai-windows.html'>,</span>
<span class="n"> <Element 'a' href='http://www.familug.org/2018/04/alsa-pulseaudio-jack-ubuntu.html'>,</span>
<span class="n"> <Element 'a' href='http://www.familug.org/2018/02/hello-2018.html'>,</span>
<span class="n"> <Element 'a' href='http://www.familug.org/2017/12/dung-git-diff-voi-patch.html'>,</span>
<span class="n"> <Element 'a' href='http://www.familug.org/2017/11/pgrep-e-grep-process.html'>,</span>
<span class="n"> <Element 'a' href='http://www.familug.org/2017/11/vuot-qua-gioi-han-1-deploy-key-cua.html'></span><span class="o">]</span><span class="w"></span>
<span class="ow">In</span><span class="w"> </span><span class="o">[</span><span class="n">26</span><span class="o">]</span><span class="err">:</span><span class="w"> </span><span class="n">r</span><span class="p">.</span><span class="n">html</span><span class="p">.</span><span class="n">xpath</span><span class="p">(</span><span class="s1">'//h3//a/@href'</span><span class="p">)</span><span class="w"></span>
<span class="k">Out</span><span class="o">[</span><span class="n">26</span><span class="o">]</span><span class="err">:</span><span class="w"></span>
<span class="o">[</span><span class="n">'http://www.familug.org/2018/04/uefi-vs-bios-tao-usb-boot-cai-windows.html',</span>
<span class="n">'http://www.familug.org/2018/04/alsa-pulseaudio-jack-ubuntu.html',</span>
<span class="n">'http://www.familug.org/2018/02/hello-2018.html',</span>
<span class="n">'http://www.familug.org/2017/12/dung-git-diff-voi-patch.html',</span>
<span class="n">'http://www.familug.org/2017/11/pgrep-e-grep-process.html',</span>
<span class="n">'http://www.familug.org/2017/11/vuot-qua-gioi-han-1-deploy-key-cua.html'</span><span class="o">]</span><span class="w"></span>
</code></pre></div>
<h3>CSS Selector</h3>
<div class="highlight"><pre><span></span><code>In [26]: sel = '#Blog1 > div.blog-posts.hfeed > div:nth-child(1) > div > div > div > h3'
In [27]: r.html.find(sel, first=True)
Out[27]: <Element 'h3' class=('post-title', 'entry-title') itemprop='name'>
In [28]: r.html.find(sel, first=True).text
Out[28]: 'UEFI vs BIOS - tạo USB boot cài Windows 10 từ Ubuntu'
</code></pre></div>
<p>CSS selector này có thể copy từ developer tool của trình duyệt web.</p>
<h2>Tổng kết</h2>
<p>Requests-html là 1 thư viện tiện lợi thay thế cho combo requests + bs4, dễ dàng chuyển lên scrapy nếu cần thực hiện dự án crawling lớn, chuyên nghiệp.</p>
<p>Chi tiết hơn xem tại: http://html.python-requests.org/.</p>
<p>HẾT.</p>
<p>TUNG491 at https://pymi.vn</p>python -i file.py2018-05-04T00:00:00+07:002018-05-04T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-05-04:/article/pythoni/<p>chạy lệnh python với option -i</p><p>Với một lập trình viên Python, quá trình sản xuất code là liên tục vòng tuần
hoàn:</p>
<div class="highlight"><pre><span></span><code><span class="n">viết</span> <span class="n">code</span> <span class="o">-></span> <span class="n">python</span> <span class="n">filename</span><span class="p">.</span><span class="n">py</span> <span class="o">-></span> <span class="n">sửa</span> <span class="n">code</span> <span class="o">-></span> <span class="n">python</span> <span class="n">filename</span><span class="p">.</span><span class="n">py</span> <span class="p">...</span>
</code></pre></div>
<p>cho đến khi mệt nghỉ, hoặc vợ gọi.</p>
<p>Option <code>-i</code> mang lại một khả năng khá thú vị khi cho phép tiếp tục tương tác
với "đoạn code" sau khi đã chạy xong.</p>
<p>Ví dụ, đoạn code:</p>
<div class="highlight"><pre><span></span><code><span class="n">a</span> <span class="o">=</span> <span class="mi">6</span>
<span class="n">b</span> <span class="o">=</span> <span class="mi">9</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<span class="n">hot_links</span> <span class="o">=</span> <span class="p">[</span><span class="s1">'https://pymi.vn'</span><span class="p">,</span> <span class="s1">'http://www.familug.org'</span><span class="p">,</span> <span class="s1">'http://pp.pymi.vn'</span><span class="p">]</span>
<span class="k">for</span> <span class="n">link</span> <span class="ow">in</span> <span class="n">hot_links</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">link</span><span class="p">)</span>
</code></pre></div>
<p>Chạy</p>
<div class="highlight"><pre><span></span><code>$ python3 get_data.py
https://pymi.vn
http://www.familug.org
http://pp.pymi.vn
</code></pre></div>
<p>Giờ muốn tải từng link về thì phải mở editor lên, sửa code và chạy thử tiếp...</p>
<p>Chạy với <code>-i</code>, ta có:</p>
<div class="highlight"><pre><span></span><code>$ python3 -i get_data.py
https://pymi.vn
http://www.familug.org
http://pp.pymi.vn
>>> locals<span class="o">()</span>
<span class="o">{</span><span class="s1">'link'</span>: <span class="s1">'http://pp.pymi.vn'</span>, <span class="s1">'__doc__'</span>: None, <span class="s1">'c'</span>: <span class="m">15</span>, <span class="s1">'a'</span>: <span class="m">6</span>, <span class="s1">'__name__'</span>: <span class="s1">'__main__'</span>, <span class="s1">'__builtins__'</span>: <module <span class="s1">'builtins'</span> <span class="o">(</span>built-in<span class="o">)</span>>, <span class="s1">'__spec__'</span>: None, <span class="s1">'b'</span>: <span class="m">9</span>, <span class="s1">'__cached__'</span>: None, <span class="s1">'__loader__'</span>: <_frozen_importlib_external.SourceFileLoader object at 0x7f0bcaee7f28>, <span class="s1">'hot_links'</span>: <span class="o">[</span><span class="s1">'https://pymi.vn'</span>, <span class="s1">'http://www.familug.org'</span>, <span class="s1">'http://pp.pymi.vn'</span><span class="o">]</span>, <span class="s1">'__package__'</span>: None<span class="o">}</span>
>>> c
<span class="m">15</span>
>>> hot_links
<span class="o">[</span><span class="s1">'https://pymi.vn'</span>, <span class="s1">'http://www.familug.org'</span>, <span class="s1">'http://pp.pymi.vn'</span><span class="o">]</span>
</code></pre></div>
<p>Tất nhiên các Pymier chẳng ai dùng python chay cả, mà là ipython:</p>
<div class="highlight"><pre><span></span><code>$ ipython -i get_data.py
Python <span class="m">3</span>.5.2 <span class="o">(</span>default, Nov <span class="m">17</span> <span class="m">2016</span>, <span class="m">17</span>:05:23<span class="o">)</span>
Type <span class="s1">'copyright'</span>, <span class="s1">'credits'</span> or <span class="s1">'license'</span> <span class="k">for</span> more information
IPython <span class="m">6</span>.2.1 -- An enhanced Interactive Python. Type <span class="s1">'?'</span> <span class="k">for</span> help.
https://pymi.vn
http://www.familug.org
http://pp.pymi.vn
In <span class="o">[</span><span class="m">1</span><span class="o">]</span>: hot_links
Out<span class="o">[</span><span class="m">1</span><span class="o">]</span>: <span class="o">[</span><span class="s1">'https://pymi.vn'</span>, <span class="s1">'http://www.familug.org'</span>, <span class="s1">'http://pp.pymi.vn'</span><span class="o">]</span>
</code></pre></div>
<p>Một option đơn giản, nhưng vô cùng tiện lợi.</p>
<div class="highlight"><pre><span></span><code>-i : inspect interactively after running script; forces a prompt even
</code></pre></div>
<p>Hết.
HVN at https://pymi.vn and http://www.familug.org</p>Blog chung của các Pymier2018-05-02T00:00:00+07:002018-05-02T00:00:00+07:00hvnsweetingtag:pp.pymi.vn,2018-05-02:/article/first/<p>Bài viết đầu tiên</p><p>Nơi các Pymier chia sẻ kiến thức cho nhau.
Để tạo một bài viết - hãy fork repo này và tạo một Pull Requests,
sau khi review xong bài viết sẽ được xuất bản.</p>
<ul>
<li>Các bài viết về Python nên đặt trong Category <code>Python</code>, và có tag <code>python</code></li>
<li>Tên tác giả nên dùng GitHub username</li>
<li>Bài viết sử dụng Markdown syntax</li>
<li>Bài viết tuyển chọn sẽ được đưa vào category "Trang chủ"</li>
</ul>