| CARVIEW |
Select Language
HTTP/2 200
server: GitHub.com
content-type: application/xml
last-modified: Thu, 17 Apr 2025 02:20:51 GMT
access-control-allow-origin: *
etag: W/"68006583-14297a"
expires: Fri, 23 Jan 2026 02:25:21 GMT
cache-control: max-age=600
content-encoding: gzip
x-proxy-cache: MISS
x-github-request-id: C8E0:1B8791:6E7C0:81AF7:6972D9B8
accept-ranges: bytes
age: 0
date: Fri, 23 Jan 2026 02:15:21 GMT
via: 1.1 varnish
x-served-by: cache-bom-vanm7210082-BOM
x-cache: MISS
x-cache-hits: 0
x-timer: S1769134522.508364,VS0,VE272
vary: Accept-Encoding
x-fastly-request-id: 1080cd80ba452cea7deb0beed121bd880e5c89aa
content-length: 185080
Planeta PythonBrasil
https://planet.python.org.br/
Planeta PythonBrasil - https://planet.python.org.br/
-
Rodrigo Delduca: Building and Publishing Games to Steam Directly from GitHub Actions
https://nullonerror.org/2025/03/23/building-and-publishing-games-to-steam-directly-from-gitHub-actions/
<p>I have been using GitHub Actions extensively both at work and in personal projects, as shown in this post <a href="https://nullonerror.org/2023/11/01/what-i-ve-been-automating-with-github-actions-an-automated-life/">What I’ve been automating with GitHub Actions, an automated life</a>.</p>
<p>In my free time, I’m working on a 2D hide-and-seek game, and as you might expect, I’ve automated the entire release pipeline for publishing on Steam. After a few attempts, when it finally worked, it felt like magic: all I had to do was create a new tag, and within minutes, the Steam client was downloading the update.</p>
<p>As I mentioned earlier, I have a 2D engine that, while simple, is quite comprehensive. With each new tag, I compile it in parallel for Windows, macOS, Linux, and WebAssembly. Once compilation is complete, I create a release and publish it on GitHub. <a href="https://github.com/willtobyte/carimbo/releases">Releases · willtobyte/carimbo</a></p>
<p>This way</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Release</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">push</span><span class="pi">:</span>
<span class="na">tags</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">v*.*.*"</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">release</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">${{ matrix.config.os }}</span>
<span class="na">permissions</span><span class="pi">:</span>
<span class="na">contents</span><span class="pi">:</span> <span class="s">write</span>
<span class="na">strategy</span><span class="pi">:</span>
<span class="na">fail-fast</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">matrix</span><span class="pi">:</span>
<span class="na">config</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">macOS</span>
<span class="na">os</span><span class="pi">:</span> <span class="s">macos-latest</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Ubuntu</span>
<span class="na">os</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">WebAssembly</span>
<span class="na">os</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Windows</span>
<span class="na">os</span><span class="pi">:</span> <span class="s">windows-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Cache Dependencies</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/cache@v4</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">~/.conan2/p</span>
<span class="s">C:/Users/runneradmin/.conan2/p</span>
<span class="na">key</span><span class="pi">:</span> <span class="s">${{ matrix.config.name }}-${{ hashFiles('**/conanfile.py') }}</span>
<span class="na">restore-keys</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">${{ matrix.config.name }}-</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Prepare Build Directory</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">mkdir build</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup Python</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-python@v5</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">python-version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.12"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Conan</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">pip install conan</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Detect Conan Profile</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">conan profile detect --force</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set Conan Center</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">conan remote update conancenter --url https://center2.conan.io</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Detect WebAssembly Conan Profile</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">matrix.config.name == 'WebAssembly'</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">cat > ~/.conan2/profiles/webassembly <<EOF</span>
<span class="s">include(default)</span>
<span class="s">[settings]</span>
<span class="s">arch=wasm</span>
<span class="s">os=Emscripten</span>
<span class="s">[tool_requires]</span>
<span class="s">*: emsdk/3.1.73</span>
<span class="s">EOF</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Windows Or macOS Dependencies</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">matrix.config.name == 'Windows' || matrix.config.name == 'macOS'</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">conan install . --output-folder=build --build=missing --settings compiler.cppstd=20 --settings build_type=Release</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install Ubuntu Dependencies</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">matrix.config.name == 'Ubuntu'</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">conan install . --output-folder=build --build=missing --settings compiler.cppstd=20 --settings build_type=Release --conf "tools.system.package_manager:mode=install" --conf "tools.system.package_manager:sudo=True"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install WebAssembly Dependencies</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">matrix.config.name == 'WebAssembly'</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">conan install . --output-folder=build --build=missing --profile=webassembly --settings compiler.cppstd=20 --settings build_type=Release</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Configure</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">cmake .. -DCMAKE_TOOLCHAIN_FILE="conan_toolchain.cmake" -DCMAKE_BUILD_TYPE=Release</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">build</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">cmake --build . --parallel 8 --config Release --verbose</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">build</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create Artifacts Directory</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">mkdir artifacts</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Compress Artifacts</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">matrix.config.name == 'macOS'</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">chmod -R a+rwx carimbo</span>
<span class="s">tar -cpzvf macOS.tar.gz carimbo</span>
<span class="s">mv macOS.tar.gz ../artifacts</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Compress Artifacts</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">matrix.config.name == 'Ubuntu'</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">chmod +x carimbo</span>
<span class="s">tar -czvf Ubuntu.tar.gz --mode='a+rwx' carimbo</span>
<span class="s">mv Ubuntu.tar.gz ../artifacts</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Compress Artifacts</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">matrix.config.name == 'WebAssembly'</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">zip -jr WebAssembly.zip carimbo.wasm carimbo.js</span>
<span class="s">mv WebAssembly.zip ../artifacts</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Compress Artifacts</span>
<span class="na">if</span><span class="pi">:</span> <span class="s">matrix.config.name == 'Windows'</span>
<span class="na">working-directory</span><span class="pi">:</span> <span class="s">build</span>
<span class="na">shell</span><span class="pi">:</span> <span class="s">powershell</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">Compress-Archive -LiteralPath 'Release/carimbo.exe' -DestinationPath "../artifacts/Windows.zip"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Release</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">softprops/action-gh-release@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">tag_name</span><span class="pi">:</span> <span class="s">${{ github.event.inputs.tagName }}</span>
<span class="na">prerelease</span><span class="pi">:</span> <span class="s">${{ github.events.inputs.prerelease }}</span>
<span class="na">files</span><span class="pi">:</span> <span class="s">artifacts/*</span>
</code></pre></div></div>
<p>Publishing on Steam is quite simple. First, you need a developer account with the correct documentation and fees paid.</p>
<p>After that, you’ll need to generate some secret keys as follows:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>steamcmd +login <username> <password> +quit
</code></pre></div></div>
<p>If you don’t have the steamcmd application installed, you’ll need to install it using:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cast <span class="nb">install</span> <span class="nt">--cask</span> steamcmd
</code></pre></div></div>
<p>Copy the contents of the authentication file:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cat</span> ~/Library/Application<span class="se">\ </span>Support/Steam/config/config.vdf | <span class="nb">base64</span> | pbcopy
</code></pre></div></div>
<p><strong>Note:</strong> You must have MFA enabled. After logging in, run the command below and copy the output into a GitHub Action variable named <code class="language-plaintext highlighter-rouge">STEAM_CONFIG_VDF</code>.</p>
<p>Also, create the variables <code class="language-plaintext highlighter-rouge">STEAM_USERNAME</code> with your username and <code class="language-plaintext highlighter-rouge">STEAM_APP_ID</code> with your game’s ID.</p>
<p>Additionally, the Action downloads the latest Carimbo release for Windows only (sorry Linux and macOS users, my time is limited). Ideally, I should pin the runtime version (the Carimbo version) using something like a runtime.txt file. Maybe I’ll implement this in the future, but for now, everything runs on the bleeding edge. :-)</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span>
<span class="na">push</span><span class="pi">:</span>
<span class="na">tags</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">v*.*.*"</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">publish</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">CARIMBO_TAG</span><span class="pi">:</span> <span class="s2">"</span><span class="s">v1.0.65"</span>
<span class="na">permissions</span><span class="pi">:</span>
<span class="na">contents</span><span class="pi">:</span> <span class="s">write</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Clone the repository</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install 7zip</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">sudo apt install p7zip-full</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Create bundle</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">7z a -xr'!.git/*' -xr'!.git' -xr'!.*' -t7z -m0=lzma -mx=6 -mfb=64 -md=32m -ms=on bundle.7z .</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Download Carimbo runtime</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">GITHUB_TOKEN</span><span class="pi">:</span> <span class="s">${{ secrets.GITHUB_TOKEN }}</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">gh release download ${{ env.CARIMBO_TAG }} --repo willtobyte/carimbo --pattern "Windows.zip"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Extract Carimbo runtime</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">7z x Windows.zip -o.</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Copy files</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">mkdir -p output</span>
<span class="s">mv bundle.7z output/</span>
<span class="s">mv carimbo.exe output/</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload build to Steam</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">game-ci/steam-deploy@v3</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">username</span><span class="pi">:</span> <span class="s">${{ secrets.STEAM_USERNAME }}</span>
<span class="na">configVdf</span><span class="pi">:</span> <span class="s">${{ secrets.STEAM_CONFIG_VDF }}</span>
<span class="na">appId</span><span class="pi">:</span> <span class="s">${{ secrets.STEAM_APP_ID }}</span>
<span class="na">rootPath</span><span class="pi">:</span> <span class="s">output</span>
<span class="na">depot1Path</span><span class="pi">:</span> <span class="s2">"</span><span class="s">."</span>
<span class="na">releaseBranch</span><span class="pi">:</span> <span class="s">prerelease</span>
</code></pre></div></div>
<p>Boom! If everything is correct, your game should appear in your Steam client under the list of owned games.</p>
2025-03-23T00:00:00+00:00
Rodrigo Delduca
-
Nilo Ney Coutinho Menezes: Advent of code 2024 - Day 15 of 25
https://blog.nilo.pro.br/en/posts/2024-12-16-adventofcode2024-day15/
<p>The fifteenth day of Advent of Code was yesterday 15/12/2024!
If you&rsquo;re here for the first time today, don&rsquo;t forget to read the posts about
<a href="https://blog.nilo.pro.br/en/posts/2024-12-06-adventofcode2024-day01/">Day 1</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-06-adventofcode2024-day02/">Day 2</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-06-adventofcode2024-day03/">Day 3</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-06-adventofcode2024-day04/">Day 4</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-06-adventofcode2024-day05/">Day 5</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-07-adventofcode2024-day06/">Day 6</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-08-adventofcode2024-day07/">Day 7</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-09-adventofcode2024-day08/">Day 8</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-10-adventofcode2024-day09/">Day 9</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-11-adventofcode2024-day10/">Day 10</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-12-adventofcode2024-day11/">Day 11</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-13-adventofcode2024-day12/">Day 12</a>,
<a href="https://blog.nilo.pro.br/en/posts/2024-12-14-adventofcode2024-day13/">Day 13</a> and
<a href="https://blog.nilo.pro.br/en/posts/2024-12-15-adventofcode2024-day14/">Day 14</a>.</p>
<p>I classified the <a href="https://adventofcode.com/2024/day/15">fifteenth problem</a> as medium (part 1) and medium (part 2).</p>
<h1 id="tips">Tips</h1>
<ul>
<li>The first part of the problem is to read the maze (warehouse) and the robot commands.</li>
<li>Use the example inputs to test the solution.</li>
<li>You need to detect in the maze input where the walls, robot, and boxes are.</li>
<li>The commands are on multiple lines, it&rsquo;s easier if you concatenate them into a single string.</li>
<li>You can implement robot directions (commands) using vectors.</li>
<li>Vectors can also be used to mark box and wall positions.</li>
<li>The walls surround the entire warehouse, you don&rsquo;t need to worry about the robot leaving the warehouse.</li>
<li>To check if the robot can move, verify if the new position is free. In this case, move the robot by updating its position.</li>
<li>If the next position is occupied by a wall, the robot cannot move and stays in the same place.</li>
<li>To know if boxes can be moved, you can recursively try all boxes from the movement direction. For each box, check if it can move (blank space in the movement direction). If one of the boxes cannot be moved, the movement is canceled.</li>
<li>Remember that the robot can push multiple boxes at once. A recursive function can help solve this problem.</li>
<li>In part 1, boxes are the same size as walls, meaning each box occupies one position. In part 2, each box occupies two positions.</li>
<li>Part 2 is a bit more complex because you can move more than columns and rows of boxes, but entire parts of the warehouse.</li>
<li>Horizontal movement is similar to part 1, as each box has a height of only one character, like in part 1.</li>
<li>Each movement in part 2 can move up to 2 boxes at once. But this movement is done in cascade, so these two boxes can move other 2, 3, or more boxes. Each box movement can move zero, one, or two boxes at each step, but can cause the movement of many others.</li>
<li>In part 2, you need to verify if the entire movement is valid. For example, when the boxes being pushed form a V or get stuck on a wall, they all have to stop moving. If you implement the movement function as in part 1, you&rsquo;ll see that the boxes will move in several pieces, which leads to the wrong answer.</li>
</ul>
<h1 id="how-much-python-do-you-need-to-know">How much Python do you need to know?</h1>
<p>The chapters refer to my book <a href="https://python.nilo.pro.br/">Introduction to Programming with Python</a>.</p>
<ul>
<li>Lists - Chapter 6</li>
<li>Strings - Chapter 7</li>
<li>Recursive Functions - Chapter 8</li>
<li>Classes, inheritance - Chapter 10</li>
</ul>
<h1 id="answer">Answer</h1>
<p>Here&rsquo;s the code that solves the problem. You&rsquo;ll see the scars in the code from switching between part 1 and part 2, but the code works relatively well.</p>
<p>You can use the Warehouse, Boxes, Wall, Robot, Problem, and Problem2 classes to debug your solution. The <code>draw</code> and <code>prompt</code> methods can help interpret and draw the commands step by step. Although they&rsquo;re not called in the answer, they&rsquo;re in the code if you need them.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> sys
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> helpers <span style="color:#f92672">import</span> read_file_without_blank_lines, Vector2d, Maze
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>TEST_INPUT <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">########
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#..O.O.#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">##@.O..#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#...O..#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#.#.O..#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#...O..#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#......#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">########
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;^^&gt;&gt;&gt;vv&lt;v&gt;&gt;v&lt;&lt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>INPUT <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">##########
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#..O..O.O#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#......O.#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#.OO..O.O#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#..O@..O.#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#O#..O...#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#O..O..O.#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#.OO.O.OO#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#....O...#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">##########
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;vv&gt;^&lt;v^&gt;v&gt;^vv^v&gt;v&lt;&gt;v^v&lt;v&lt;^vv&lt;&lt;&lt;^&gt;&lt;&lt;&gt;&lt;&gt;&gt;v&lt;vvv&lt;&gt;^v^&gt;^&lt;&lt;&lt;&gt;&lt;&lt;v&lt;&lt;&lt;v^vv^v&gt;^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">vvv&lt;&lt;^&gt;^v^^&gt;&lt;&lt;&gt;&gt;&gt;&lt;&gt;^&lt;&lt;&gt;&lt;^vv^^&lt;&gt;vvv&lt;&gt;&gt;&lt;^^v&gt;^&gt;vv&lt;&gt;v&lt;&lt;&lt;&lt;v&lt;^v&gt;^&lt;^^&gt;&gt;&gt;^&lt;v&lt;v
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&gt;&lt;&gt;vv&gt;v^v^&lt;&gt;&gt;&lt;&gt;&gt;&gt;&gt;&lt;^^&gt;vv&gt;v&lt;^^^&gt;&gt;v^v^&lt;^^&gt;v^^&gt;v^&lt;^v&gt;v&lt;&gt;&gt;v^v^&lt;v&gt;v^^&lt;^^vv&lt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;&lt;v&lt;^&gt;&gt;^^^^&gt;&gt;&gt;v^&lt;&gt;vvv^&gt;&lt;v&lt;&lt;&lt;&gt;^^^vv^&lt;vvv&gt;^&gt;v&lt;^^^^v&lt;&gt;^&gt;vvvv&gt;&lt;&gt;&gt;v^&lt;&lt;^^^^^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">^&gt;&lt;^&gt;&lt;&gt;&gt;&gt;&lt;&gt;^^&lt;&lt;^^v&gt;&gt;&gt;&lt;^&lt;v&gt;^&lt;vv&gt;&gt;v&gt;&gt;&gt;^v&gt;&lt;&gt;^v&gt;&lt;&lt;&lt;&lt;v&gt;&gt;v&lt;v&lt;v&gt;vvv&gt;^&lt;&gt;&lt;&lt;&gt;^&gt;&lt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">^&gt;&gt;&lt;&gt;^v&lt;&gt;&lt;^vvv&lt;^^&lt;&gt;&lt;v&lt;&lt;&lt;&lt;&lt;&gt;&lt;^v&lt;&lt;&lt;&gt;&lt;&lt;&lt;^^&lt;v&lt;^^^&gt;&lt;^&gt;&gt;^&lt;v^&gt;&lt;&lt;&lt;^&gt;&gt;^v&lt;v^v&lt;v^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&gt;^&gt;&gt;^v&gt;vv&gt;^&lt;&lt;^v&lt;&gt;&gt;&lt;&lt;&gt;&lt;&lt;v&lt;&lt;v&gt;&lt;&gt;v&lt;^vv&lt;&lt;&lt;&gt;^^v^&gt;^^&gt;&gt;&gt;&lt;&lt;^v&gt;&gt;v^v&gt;&lt;^^&gt;&gt;^&lt;&gt;vv^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;&gt;&lt;^^&gt;^^^&lt;&gt;&lt;vvvvv^v&lt;v&lt;&lt;&gt;^v&lt;v&gt;v&lt;&lt;^&gt;&lt;&lt;&gt;&lt;&lt;&gt;&lt;&lt;&lt;^^&lt;&lt;&lt;^&lt;&lt;&gt;&gt;&lt;&lt;&gt;&lt;^^^&gt;^^&lt;&gt;^&gt;v&lt;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">^^&gt;vv&lt;^v^v&lt;vv&gt;^&lt;&gt;&lt;v&lt;^v&gt;^^^&gt;&gt;&gt;^^vvv^&gt;vvv&lt;&gt;&gt;&gt;^&lt;^&gt;&gt;&gt;&gt;&gt;^&lt;&lt;^v&gt;^vvv&lt;&gt;^&lt;&gt;&lt;&lt;v&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">v^^&gt;&gt;&gt;&lt;&lt;^^&lt;&gt;&gt;^v^&lt;v^vv&lt;&gt;v^&lt;&lt;&gt;^&lt;^v^v&gt;&lt;^&lt;&lt;&lt;&gt;&lt;&lt;^&lt;v&gt;&lt;v&lt;&gt;vv&gt;&gt;v&gt;&lt;v^&lt;vv&lt;&gt;v^&lt;&lt;^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">read_input</span>(input: str) <span style="color:#f92672">-&gt;</span> tuple[str, str]:
</span></span><span style="display:flex;"><span> maze <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span> commands <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> input<span style="color:#f92672">.</span>splitlines():
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> line<span style="color:#f92672">.</span>strip():
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> line<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#34;#&#34;</span>):
</span></span><span style="display:flex;"><span> maze<span style="color:#f92672">.</span>append(line)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span> commands<span style="color:#f92672">.</span>append(line)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> maze, commands
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Vector2dWithSymbol</span>(Vector2d):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, y, x, symbol):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(y, x)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>symbol <span style="color:#f92672">=</span> symbol
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __add__(self, other):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> Vector2dWithSymbol(self<span style="color:#f92672">.</span>y <span style="color:#f92672">+</span> other<span style="color:#f92672">.</span>y, self<span style="color:#f92672">.</span>x <span style="color:#f92672">+</span> other<span style="color:#f92672">.</span>x, self<span style="color:#f92672">.</span>symbol)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __sub__(self, other):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> Vector2dWithSymbol(self<span style="color:#f92672">.</span>y <span style="color:#f92672">-</span> other<span style="color:#f92672">.</span>y, self<span style="color:#f92672">.</span>x <span style="color:#f92672">-</span> other<span style="color:#f92672">.</span>x, self<span style="color:#f92672">.</span>symbol)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Box</span>(Vector2dWithSymbol):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, y, x, symbol<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;O&#34;</span>):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(y, x, symbol)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Wall</span>(Vector2dWithSymbol):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, y, x):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(y, x, <span style="color:#e6db74">&#34;#&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Robot</span>(Vector2dWithSymbol):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, y, x):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(y, x, <span style="color:#e6db74">&#34;@&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Warehouse</span>(Maze):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">init_drawing</span>(self, background<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;.&#34;</span>):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>background <span style="color:#f92672">=</span> background
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>drawing <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(self<span style="color:#f92672">.</span>max_y):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>drawing<span style="color:#f92672">.</span>append([background] <span style="color:#f92672">*</span> self<span style="color:#f92672">.</span>max_x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">draw</span>(self, objects: list[Vector2dWithSymbol]):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> obj <span style="color:#f92672">in</span> objects:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>drawing[obj<span style="color:#f92672">.</span>y][obj<span style="color:#f92672">.</span>x] <span style="color:#f92672">=</span> obj<span style="color:#f92672">.</span>symbol
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">dump_drawing</span>(self, output<span style="color:#f92672">=</span>sys<span style="color:#f92672">.</span>stdout):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> line <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>drawing:
</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;&#34;</span><span style="color:#f92672">.</span>join(line), file<span style="color:#f92672">=</span>output)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __iter__(self):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> iter(self<span style="color:#f92672">.</span>maze)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Problem</span>:
</span></span><span style="display:flex;"><span> DIRECTIONS <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;&lt;&#34;</span>: Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>),
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;^&#34;</span>: Vector2d(<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">0</span>),
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;&gt;&#34;</span>: Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>),
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;v&#34;</span>: Vector2d(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">0</span>),
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, maze: list[str], commands: list[str]):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>commands <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span><span style="color:#f92672">.</span>join(commands)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>warehouse <span style="color:#f92672">=</span> Warehouse(maze)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>setup()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">setup</span>(self):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>walls <span style="color:#f92672">=</span> set()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>boxes <span style="color:#f92672">=</span> dict()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robot <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> y, line <span style="color:#f92672">in</span> enumerate(self<span style="color:#f92672">.</span>warehouse):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> x, char <span style="color:#f92672">in</span> enumerate(line):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;#&#34;</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>walls<span style="color:#f92672">.</span>add(Wall(y, x))
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;O&#34;</span>:
</span></span><span style="display:flex;"><span> box <span style="color:#f92672">=</span> Box(y, x)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>boxes[box] <span style="color:#f92672">=</span> box
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;@&#34;</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robot <span style="color:#f92672">=</span> Robot(y, x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">draw</span>(self):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>warehouse<span style="color:#f92672">.</span>init_drawing()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>warehouse<span style="color:#f92672">.</span>draw(self<span style="color:#f92672">.</span>walls)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>warehouse<span style="color:#f92672">.</span>draw(self<span style="color:#f92672">.</span>boxes)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>warehouse<span style="color:#f92672">.</span>draw([self<span style="color:#f92672">.</span>robot])
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>warehouse<span style="color:#f92672">.</span>dump_drawing()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">execute_commands</span>(self):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> command <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>commands:
</span></span><span style="display:flex;"><span> direction <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>DIRECTIONS[command]
</span></span><span style="display:flex;"><span> new_position <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>robot <span style="color:#f92672">+</span> direction
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> new_position <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>boxes:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> self<span style="color:#f92672">.</span>push(new_position, direction):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> new_position <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>walls:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robot <span style="color:#f92672">+=</span> direction
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">push</span>(self, position: Vector2d, direction: Vector2d, execute: bool <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>):
</span></span><span style="display:flex;"><span> new_position <span style="color:#f92672">=</span> position <span style="color:#f92672">+</span> direction
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> new_position <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>walls:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> new_position <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>boxes:
</span></span><span style="display:flex;"><span> can_push <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>push(new_position, direction, execute)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> can_push:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> execute:
</span></span><span style="display:flex;"><span> box <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>boxes<span style="color:#f92672">.</span>pop(position)
</span></span><span style="display:flex;"><span> box<span style="color:#f92672">.</span>y <span style="color:#f92672">=</span> new_position<span style="color:#f92672">.</span>y
</span></span><span style="display:flex;"><span> box<span style="color:#f92672">.</span>x <span style="color:#f92672">=</span> new_position<span style="color:#f92672">.</span>x
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>boxes[box] <span style="color:#f92672">=</span> box
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">gps_boxes</span>(self):
</span></span><span style="display:flex;"><span> total <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> box <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>boxes:
</span></span><span style="display:flex;"><span> total <span style="color:#f92672">+=</span> box<span style="color:#f92672">.</span>y <span style="color:#f92672">*</span> <span style="color:#ae81ff">100</span> <span style="color:#f92672">+</span> box<span style="color:#f92672">.</span>x
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> total
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">prompt</span>(self):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>draw()
</span></span><span style="display:flex;"><span> commands <span style="color:#f92672">=</span> input(<span style="color:#e6db74">&#34;Commands: &#34;</span>)<span style="color:#f92672">.</span>lower()<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> commands:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>commands <span style="color:#f92672">=</span> commands
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>execute_commands()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Problem2</span>(Problem):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, maze: list[str], commands: list[str]):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(maze, commands)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>warehouse<span style="color:#f92672">.</span>max_x <span style="color:#f92672">*=</span> <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">setup</span>(self):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>walls <span style="color:#f92672">=</span> set()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>boxes <span style="color:#f92672">=</span> dict()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robot <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> y, line <span style="color:#f92672">in</span> enumerate(self<span style="color:#f92672">.</span>warehouse):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> x, char <span style="color:#f92672">in</span> enumerate(line):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;#&#34;</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>walls<span style="color:#f92672">.</span>add(Wall(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>))
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>walls<span style="color:#f92672">.</span>add(Wall(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;O&#34;</span>:
</span></span><span style="display:flex;"><span> box1 <span style="color:#f92672">=</span> Box(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>, <span style="color:#e6db74">&#34;[&#34;</span>)
</span></span><span style="display:flex;"><span> box2 <span style="color:#f92672">=</span> Box(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#34;]&#34;</span>)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>boxes[box1] <span style="color:#f92672">=</span> box1
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>boxes[box2] <span style="color:#f92672">=</span> box2
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;@&#34;</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robot <span style="color:#f92672">=</span> Robot(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">push</span>(self, position: Vector2d, direction: Vector2d, execute: bool <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>):
</span></span><span style="display:flex;"><span> new_position <span style="color:#f92672">=</span> position <span style="color:#f92672">+</span> direction
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> direction <span style="color:#f92672">in</span> [Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>), Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>)] <span style="color:#f92672">or</span> new_position <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>walls:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> super()<span style="color:#f92672">.</span>push(position, direction, execute)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> position <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>boxes:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> box1, box2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>find_box(position)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> can_push1 <span style="color:#f92672">=</span> super()<span style="color:#f92672">.</span>push(box1, direction, <span style="color:#66d9ef">False</span>)
</span></span><span style="display:flex;"><span> can_push2 <span style="color:#f92672">=</span> super()<span style="color:#f92672">.</span>push(box2, direction, <span style="color:#66d9ef">False</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> can_push1 <span style="color:#f92672">and</span> can_push2:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> execute:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>push_v(box1, box2, direction)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">find_box</span>(self, position):
</span></span><span style="display:flex;"><span> box1 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>boxes[position]
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> box1<span style="color:#f92672">.</span>symbol <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;[&#34;</span>:
</span></span><span style="display:flex;"><span> box2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>boxes[position <span style="color:#f92672">+</span> Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>)]
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span> box2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>boxes[position <span style="color:#f92672">+</span> Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)]
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> box1, box2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">push_v</span>(self, position1, position2, direction):
</span></span><span style="display:flex;"><span> np1 <span style="color:#f92672">=</span> position1 <span style="color:#f92672">+</span> direction
</span></span><span style="display:flex;"><span> np2 <span style="color:#f92672">=</span> position2 <span style="color:#f92672">+</span> direction
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> np1 <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>boxes:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>push_v(<span style="color:#f92672">*</span>self<span style="color:#f92672">.</span>find_box(np1), direction)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> np2 <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>boxes:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>push_v(<span style="color:#f92672">*</span>self<span style="color:#f92672">.</span>find_box(np2), direction)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> position, new_position <span style="color:#f92672">in</span> zip([position1, position2], [np1, np2]):
</span></span><span style="display:flex;"><span> box <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>boxes<span style="color:#f92672">.</span>pop(position)
</span></span><span style="display:flex;"><span> box<span style="color:#f92672">.</span>y <span style="color:#f92672">=</span> new_position<span style="color:#f92672">.</span>y
</span></span><span style="display:flex;"><span> box<span style="color:#f92672">.</span>x <span style="color:#f92672">=</span> new_position<span style="color:#f92672">.</span>x
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>boxes[box] <span style="color:#f92672">=</span> box
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">gps_boxes</span>(self):
</span></span><span style="display:flex;"><span> total <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> box <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>boxes:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> box<span style="color:#f92672">.</span>symbol <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;[&#34;</span>:
</span></span><span style="display:flex;"><span> total <span style="color:#f92672">+=</span> box<span style="color:#f92672">.</span>y <span style="color:#f92672">*</span> <span style="color:#ae81ff">100</span> <span style="color:#f92672">+</span> box<span style="color:#f92672">.</span>x
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> total
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problem <span style="color:#f92672">=</span> Problem(<span style="color:#f92672">*</span>read_input(TEST_INPUT))
</span></span><span style="display:flex;"><span>problem<span style="color:#f92672">.</span>execute_commands()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problem<span style="color:#f92672">.</span>gps_boxes()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Part 1 - small input test:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">2028</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problem <span style="color:#f92672">=</span> Problem(<span style="color:#f92672">*</span>read_input(INPUT))
</span></span><span style="display:flex;"><span>problem<span style="color:#f92672">.</span>execute_commands()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problem<span style="color:#f92672">.</span>gps_boxes()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Part 1 - large input test:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">10092</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problem <span style="color:#f92672">=</span> Problem(<span style="color:#f92672">*</span>read_input(read_file_without_blank_lines(<span style="color:#e6db74">&#34;15/input.txt&#34;</span>)))
</span></span><span style="display:flex;"><span>problem<span style="color:#f92672">.</span>execute_commands()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problem<span style="color:#f92672">.</span>gps_boxes()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Part 1 - large input:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">1552463</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Part 2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problem <span style="color:#f92672">=</span> Problem2(<span style="color:#f92672">*</span>read_input(INPUT))
</span></span><span style="display:flex;"><span>problem<span style="color:#f92672">.</span>execute_commands()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problem<span style="color:#f92672">.</span>gps_boxes()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Part 2 - large input test:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">9021</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problem <span style="color:#f92672">=</span> Problem2(<span style="color:#f92672">*</span>read_input(read_file_without_blank_lines(<span style="color:#e6db74">&#34;15/input.txt&#34;</span>)))
</span></span><span style="display:flex;"><span>problem<span style="color:#f92672">.</span>execute_commands()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problem<span style="color:#f92672">.</span>gps_boxes()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Part 2 - large input:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">1554058</span>
</span></span></code></pre></div>
2024-12-16T00:49:05+00:00
-
Nilo Ney Coutinho Menezes: Advent of code 2024 - Dia 15 de 25
https://blog.nilo.pro.br/posts/2024-12-16-adventofcode2024-dia15/
<p>O décimo quinto dia do Advent of Code foi ontem 15/12/2024!
Se você caiu aqui hoje pela primeira vez, não deixe de ler os posts sobre o
<a href="https://blog.nilo.pro.br/posts/2024-12-02-adventofcode2024-dia01/">Dia 1</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-03-adventofcode2024-dia02/">Dia 2</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-04-adventofcode2024-dia03/">Dia 3</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-05-adventofcode2024-dia04/">Dia 4</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-06-adventofcode2024-dia05/">Dia 5</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-07-adventofcode2024-dia06/">Dia 6</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-08-adventofcode2024-dia07/">Dia 7</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-09-adventofcode2024-dia08/">Dia 8</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-10-adventofcode2024-dia09/">Dia 9</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-11-adventofcode2024-dia10/">Dia 10</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-12-adventofcode2024-dia11/">Dia 11</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-13-adventofcode2024-dia12/">Dia 12</a>,
<a href="https://blog.nilo.pro.br/posts/2024-12-14-adventofcode2024-dia13/">Dia 13</a> e
<a href="https://blog.nilo.pro.br/posts/2024-12-15-adventofcode2024-dia14/">Dia 14</a>.</p>
<p>Classifiquei o <a href="https://adventofcode.com/2024/day/15">décimo quinto problema</a> como médio (parte 1) e médio (parte 2).</p>
<h1 id="dicas">Dicas</h1>
<ul>
<li>A primeira parte do problema é ler o labirinto (armazém) e os comandos do robô.</li>
<li>Utilize as entradas de exemplo para testar a solução.</li>
<li>Você precisa detectar na entrada do labirinto, onde estão as paredes, o robô e as caixas.</li>
<li>Os comandos estão em várias linhas, fica mais fácil se você concatená-las em uma só string.</li>
<li>Você pode implementar as direções do robô (comandos) usando vetores.</li>
<li>Vetores também podem ser utilizados para marcar as posições das caixas e paredes.</li>
<li>As paredes circundam todo o armazém, você não precisa de preocupar com o robô saindo do armazém.</li>
<li>Para verificar se o robô pode se mexer, verifique se a nova posição está livre. Neste caso, mova o robô, atualizando a posição.</li>
<li>Se a posição seguinte for ocupada por uma parede, o robô não pode se mexer e fica no mesmo lugar.</li>
<li>Para saber se pode mover as caixas, você pode tentar recursivamente todas as caixas a partir da direção do movimento. A cada caixa, verifique se ela pode se mover (espaço em branco na direção do movimento). Caso uma das caixas não possa ser movida, o movimento é cancelado.</li>
<li>Lembre-se que o robô pode empurrar várias caixas ao mesmo tempo. Uma função recursiva pode ajudar a resolver este problema.</li>
<li>Na parte 1, as caixas são do mesmo tamanho das paredes, ou seja, cada caixa ocupa uma posição. Na parte 2, cada caixa ocupa duas posições.</li>
<li>A parte 2 é um pouco mais complexa, pois você pode mover mais que colunas e linhas de caixas, mas partes inteiras do armazém.</li>
<li>O movimento horizontal é semelhante ao da parte 1, pois cada caixa tem altura de apenas um caractere, como na parte 1.</li>
<li>Cada movimento na parte 2 pode mover até 2 caixas ao mesmo tempo. Mas esse movimento é feito em cascata, logo estas duas caixas podem mover outras 2, 3, ou mais caixas. Cada movimento de caixa pode movimentar a cada passo, zero, uma ou duas caixas, mas pode ocasionar o movimento de muitas outras.</li>
<li>Na parte 2, você precisa verificar se o movimento inteiro é válido. Por exemplo, quando as caixas sendo empurradas formam um V ou ficam presas em uma parede, todas tem que parar de se mover. Se você implementar a função de movimento como na parte 1, verá que as caixas vão se mexer em vários pedaços, o que leva a resposta errada.</li>
</ul>
<h1 id="quanto-de-python-você-precisa-saber">Quanto de Python você precisa saber?</h1>
<p>Os capítulos se referem ao meu livro <a href="https://python.nilo.pro.br/">Introdução à Programação com Python</a>.</p>
<ul>
<li>Listas - Capítulo 6</li>
<li>Strings - Capítulo 7</li>
<li>Funções recursivas - Capítulo 8</li>
<li>Classes, herança - Capítulo 10</li>
</ul>
<h1 id="resposta">Resposta</h1>
<p>Aqui o código que resolve o problema. Você verá as cicatrizes no código da troca entre a parte 1 e a parte 2, mas o código funciona relativamente bem.</p>
<p>Você pode de usar as classes Armazém, Caixas, Parede, Robô, Problema e Problema2 para depurar sua solução. Os métodos <code>desenha</code>e <code>prompt</code> podem ajudar a interpretar e desenhar os comandos passo a passo. Embora não sejam chamados na resposta, eles estão no código caso você precise.</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#f92672">import</span> sys
</span></span><span style="display:flex;"><span><span style="color:#f92672">from</span> auxiliares <span style="color:#f92672">import</span> le_arquivo_sem_linhas_em_branco, Vector2d, Labirinto
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ENTRADA_TESTE <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">########
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#..O.O.#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">##@.O..#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#...O..#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#.#.O..#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#...O..#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#......#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">########
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;^^&gt;&gt;&gt;vv&lt;v&gt;&gt;v&lt;&lt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>ENTRADA <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">##########
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#..O..O.O#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#......O.#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#.OO..O.O#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#..O@..O.#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#O#..O...#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#O..O..O.#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#.OO.O.OO#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">#....O...#
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">##########
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;vv&gt;^&lt;v^&gt;v&gt;^vv^v&gt;v&lt;&gt;v^v&lt;v&lt;^vv&lt;&lt;&lt;^&gt;&lt;&lt;&gt;&lt;&gt;&gt;v&lt;vvv&lt;&gt;^v^&gt;^&lt;&lt;&lt;&gt;&lt;&lt;v&lt;&lt;&lt;v^vv^v&gt;^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">vvv&lt;&lt;^&gt;^v^^&gt;&lt;&lt;&gt;&gt;&gt;&lt;&gt;^&lt;&lt;&gt;&lt;^vv^^&lt;&gt;vvv&lt;&gt;&gt;&lt;^^v&gt;^&gt;vv&lt;&gt;v&lt;&lt;&lt;&lt;v&lt;^v&gt;^&lt;^^&gt;&gt;&gt;^&lt;v&lt;v
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&gt;&lt;&gt;vv&gt;v^v^&lt;&gt;&gt;&lt;&gt;&gt;&gt;&gt;&lt;^^&gt;vv&gt;v&lt;^^^&gt;&gt;v^v^&lt;^^&gt;v^^&gt;v^&lt;^v&gt;v&lt;&gt;&gt;v^v^&lt;v&gt;v^^&lt;^^vv&lt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;&lt;v&lt;^&gt;&gt;^^^^&gt;&gt;&gt;v^&lt;&gt;vvv^&gt;&lt;v&lt;&lt;&lt;&gt;^^^vv^&lt;vvv&gt;^&gt;v&lt;^^^^v&lt;&gt;^&gt;vvvv&gt;&lt;&gt;&gt;v^&lt;&lt;^^^^^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">^&gt;&lt;^&gt;&lt;&gt;&gt;&gt;&lt;&gt;^^&lt;&lt;^^v&gt;&gt;&gt;&lt;^&lt;v&gt;^&lt;vv&gt;&gt;v&gt;&gt;&gt;^v&gt;&lt;&gt;^v&gt;&lt;&lt;&lt;&lt;v&gt;&gt;v&lt;v&lt;v&gt;vvv&gt;^&lt;&gt;&lt;&lt;&gt;^&gt;&lt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">^&gt;&gt;&lt;&gt;^v&lt;&gt;&lt;^vvv&lt;^^&lt;&gt;&lt;v&lt;&lt;&lt;&lt;&lt;&gt;&lt;^v&lt;&lt;&lt;&gt;&lt;&lt;&lt;^^&lt;v&lt;^^^&gt;&lt;^&gt;&gt;^&lt;v^&gt;&lt;&lt;&lt;^&gt;&gt;^v&lt;v^v&lt;v^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&gt;^&gt;&gt;^v&gt;vv&gt;^&lt;&lt;^v&lt;&gt;&gt;&lt;&lt;&gt;&lt;&lt;v&lt;&lt;v&gt;&lt;&gt;v&lt;^vv&lt;&lt;&lt;&gt;^^v^&gt;^^&gt;&gt;&gt;&lt;&lt;^v&gt;&gt;v^v&gt;&lt;^^&gt;&gt;^&lt;&gt;vv^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&lt;&gt;&lt;^^&gt;^^^&lt;&gt;&lt;vvvvv^v&lt;v&lt;&lt;&gt;^v&lt;v&gt;v&lt;&lt;^&gt;&lt;&lt;&gt;&lt;&lt;&gt;&lt;&lt;&lt;^^&lt;&lt;&lt;^&lt;&lt;&gt;&gt;&lt;&lt;&gt;&lt;^^^&gt;^^&lt;&gt;^&gt;v&lt;&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">^^&gt;vv&lt;^v^v&lt;vv&gt;^&lt;&gt;&lt;v&lt;^v&gt;^^^&gt;&gt;&gt;^^vvv^&gt;vvv&lt;&gt;&gt;&gt;^&lt;^&gt;&gt;&gt;&gt;&gt;^&lt;&lt;^v&gt;^vvv&lt;&gt;^&lt;&gt;&lt;&lt;v&gt;
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">v^^&gt;&gt;&gt;&lt;&lt;^^&lt;&gt;&gt;^v^&lt;v^vv&lt;&gt;v^&lt;&lt;&gt;^&lt;^v^v&gt;&lt;^&lt;&lt;&lt;&gt;&lt;&lt;^&lt;v&gt;&lt;v&lt;&gt;vv&gt;&gt;v&gt;&lt;v^&lt;vv&lt;&gt;v^&lt;&lt;^
</span></span></span><span style="display:flex;"><span><span style="color:#e6db74">&#34;&#34;&#34;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">def</span> <span style="color:#a6e22e">le_entrada</span>(entrada: str) <span style="color:#f92672">-&gt;</span> tuple[str, str]:
</span></span><span style="display:flex;"><span> labirinto <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span> comandos <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> linha <span style="color:#f92672">in</span> entrada<span style="color:#f92672">.</span>splitlines():
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> linha<span style="color:#f92672">.</span>strip():
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> linha<span style="color:#f92672">.</span>startswith(<span style="color:#e6db74">&#34;#&#34;</span>):
</span></span><span style="display:flex;"><span> labirinto<span style="color:#f92672">.</span>append(linha)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span> comandos<span style="color:#f92672">.</span>append(linha)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> labirinto, comandos
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Vector2dComSimbolo</span>(Vector2d):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, y, x, símbolo):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(y, x)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>símbolo <span style="color:#f92672">=</span> símbolo
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __add__(self, other):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> Vector2dComSimbolo(self<span style="color:#f92672">.</span>y <span style="color:#f92672">+</span> other<span style="color:#f92672">.</span>y, self<span style="color:#f92672">.</span>x <span style="color:#f92672">+</span> other<span style="color:#f92672">.</span>x, self<span style="color:#f92672">.</span>símbolo)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __sub__(self, other):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> Vector2dComSimbolo(self<span style="color:#f92672">.</span>y <span style="color:#f92672">-</span> other<span style="color:#f92672">.</span>y, self<span style="color:#f92672">.</span>x <span style="color:#f92672">-</span> other<span style="color:#f92672">.</span>x, self<span style="color:#f92672">.</span>símbolo)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Caixas</span>(Vector2dComSimbolo):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, y, x, símbolo<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;O&#34;</span>):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(y, x, símbolo)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Parede</span>(Vector2dComSimbolo):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, y, x):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(y, x, <span style="color:#e6db74">&#34;#&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Robô</span>(Vector2dComSimbolo):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, y, x):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(y, x, <span style="color:#e6db74">&#34;@&#34;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Armazém</span>(Labirinto):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">inicie_desenho</span>(self, fundo<span style="color:#f92672">=</span><span style="color:#e6db74">&#34;.&#34;</span>):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>fundo <span style="color:#f92672">=</span> fundo
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>desenho <span style="color:#f92672">=</span> []
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> _ <span style="color:#f92672">in</span> range(self<span style="color:#f92672">.</span>max_y):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>desenho<span style="color:#f92672">.</span>append([fundo] <span style="color:#f92672">*</span> self<span style="color:#f92672">.</span>max_x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">desenhe</span>(self, objetos: list[Vector2dComSimbolo]):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> objeto <span style="color:#f92672">in</span> objetos:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>desenho[objeto<span style="color:#f92672">.</span>y][objeto<span style="color:#f92672">.</span>x] <span style="color:#f92672">=</span> objeto<span style="color:#f92672">.</span>símbolo
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">dump_desenho</span>(self, output<span style="color:#f92672">=</span>sys<span style="color:#f92672">.</span>stdout):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> linha <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>desenho:
</span></span><span style="display:flex;"><span> print(<span style="color:#e6db74">&#34;&#34;</span><span style="color:#f92672">.</span>join(linha), file<span style="color:#f92672">=</span>output)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __iter__(self):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> iter(self<span style="color:#f92672">.</span>labirinto)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Problema</span>:
</span></span><span style="display:flex;"><span> DIREÇÕES <span style="color:#f92672">=</span> {
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;&lt;&#34;</span>: Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>),
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;^&#34;</span>: Vector2d(<span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">0</span>),
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;&gt;&#34;</span>: Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>),
</span></span><span style="display:flex;"><span> <span style="color:#e6db74">&#34;v&#34;</span>: Vector2d(<span style="color:#ae81ff">1</span>, <span style="color:#ae81ff">0</span>),
</span></span><span style="display:flex;"><span> }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, labirinto: list[str], comandos: list[str]):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>comandos <span style="color:#f92672">=</span> <span style="color:#e6db74">&#34;&#34;</span><span style="color:#f92672">.</span>join(comandos)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armazém <span style="color:#f92672">=</span> Armazém(labirinto)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>monta()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">monta</span>(self):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>paredes <span style="color:#f92672">=</span> set()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>caixas <span style="color:#f92672">=</span> dict()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robô <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> y, linha <span style="color:#f92672">in</span> enumerate(self<span style="color:#f92672">.</span>armazém):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> x, char <span style="color:#f92672">in</span> enumerate(linha):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;#&#34;</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>paredes<span style="color:#f92672">.</span>add(Parede(y, x))
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;O&#34;</span>:
</span></span><span style="display:flex;"><span> caixa <span style="color:#f92672">=</span> Caixas(y, x)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>caixas[caixa] <span style="color:#f92672">=</span> caixa
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;@&#34;</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robô <span style="color:#f92672">=</span> Robô(y, x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">desenha</span>(self):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armazém<span style="color:#f92672">.</span>inicie_desenho()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armazém<span style="color:#f92672">.</span>desenhe(self<span style="color:#f92672">.</span>paredes)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armazém<span style="color:#f92672">.</span>desenhe(self<span style="color:#f92672">.</span>caixas)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armazém<span style="color:#f92672">.</span>desenhe([self<span style="color:#f92672">.</span>robô])
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armazém<span style="color:#f92672">.</span>dump_desenho()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">executa_comandos</span>(self):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> comando <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>comandos:
</span></span><span style="display:flex;"><span> direção <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>DIREÇÕES[comando]
</span></span><span style="display:flex;"><span> nova_posição <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>robô <span style="color:#f92672">+</span> direção
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> nova_posição <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>caixas:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> self<span style="color:#f92672">.</span>empurra(nova_posição, direção):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">elif</span> nova_posição <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>paredes:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">continue</span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robô <span style="color:#f92672">+=</span> direção
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">empurra</span>(self, posição: Vector2d, direção: Vector2d, executa: bool <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>):
</span></span><span style="display:flex;"><span> nova_posição <span style="color:#f92672">=</span> posição <span style="color:#f92672">+</span> direção
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> nova_posição <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>paredes:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> nova_posição <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>caixas:
</span></span><span style="display:flex;"><span> pode_empurrar <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>empurra(nova_posição, direção, executa)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> pode_empurrar:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> executa:
</span></span><span style="display:flex;"><span> caixa <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>caixas<span style="color:#f92672">.</span>pop(posição)
</span></span><span style="display:flex;"><span> caixa<span style="color:#f92672">.</span>y <span style="color:#f92672">=</span> nova_posição<span style="color:#f92672">.</span>y
</span></span><span style="display:flex;"><span> caixa<span style="color:#f92672">.</span>x <span style="color:#f92672">=</span> nova_posição<span style="color:#f92672">.</span>x
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>caixas[caixa] <span style="color:#f92672">=</span> caixa
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">gps_caixas</span>(self):
</span></span><span style="display:flex;"><span> soma <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> caixas <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>caixas:
</span></span><span style="display:flex;"><span> soma <span style="color:#f92672">+=</span> caixas<span style="color:#f92672">.</span>y <span style="color:#f92672">*</span> <span style="color:#ae81ff">100</span> <span style="color:#f92672">+</span> caixas<span style="color:#f92672">.</span>x
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> soma
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">prompt</span>(self):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">while</span> <span style="color:#66d9ef">True</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>desenha()
</span></span><span style="display:flex;"><span> comandos <span style="color:#f92672">=</span> input(<span style="color:#e6db74">&#34;Comandos: &#34;</span>)<span style="color:#f92672">.</span>lower()<span style="color:#f92672">.</span>strip()
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> <span style="color:#f92672">not</span> comandos:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">break</span>
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>comandos <span style="color:#f92672">=</span> comandos
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>executa_comandos()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">class</span> <span style="color:#a6e22e">Problema2</span>(Problema):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> __init__(self, labirinto: list[str], comandos: list[str]):
</span></span><span style="display:flex;"><span> super()<span style="color:#f92672">.</span>__init__(labirinto, comandos)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>armazém<span style="color:#f92672">.</span>max_x <span style="color:#f92672">*=</span> <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">monta</span>(self):
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>paredes <span style="color:#f92672">=</span> set()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>caixas <span style="color:#f92672">=</span> dict()
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robô <span style="color:#f92672">=</span> <span style="color:#66d9ef">None</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> y, linha <span style="color:#f92672">in</span> enumerate(self<span style="color:#f92672">.</span>armazém):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> x, char <span style="color:#f92672">in</span> enumerate(linha):
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;#&#34;</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>paredes<span style="color:#f92672">.</span>add(Parede(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>))
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>paredes<span style="color:#f92672">.</span>add(Parede(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>))
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;O&#34;</span>:
</span></span><span style="display:flex;"><span> caixa1 <span style="color:#f92672">=</span> Caixas(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>, <span style="color:#e6db74">&#34;[&#34;</span>)
</span></span><span style="display:flex;"><span> caixa2 <span style="color:#f92672">=</span> Caixas(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span> <span style="color:#f92672">+</span> <span style="color:#ae81ff">1</span>, <span style="color:#e6db74">&#34;]&#34;</span>)
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>caixas[caixa1] <span style="color:#f92672">=</span> caixa1
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>caixas[caixa2] <span style="color:#f92672">=</span> caixa2
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> char <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;@&#34;</span>:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>robô <span style="color:#f92672">=</span> Robô(y, x <span style="color:#f92672">*</span> <span style="color:#ae81ff">2</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">empurra</span>(self, posição: Vector2d, direção: Vector2d, executa: bool <span style="color:#f92672">=</span> <span style="color:#66d9ef">True</span>):
</span></span><span style="display:flex;"><span> nova_posição <span style="color:#f92672">=</span> posição <span style="color:#f92672">+</span> direção
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> direção <span style="color:#f92672">in</span> [Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>), Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>)] <span style="color:#f92672">or</span> nova_posição <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>paredes:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> super()<span style="color:#f92672">.</span>empurra(posição, direção, executa)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> posição <span style="color:#f92672">not</span> <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>caixas:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> caixa1, caixa2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>ache_caixa(posição)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> pode_empurrar1 <span style="color:#f92672">=</span> super()<span style="color:#f92672">.</span>empurra(caixa1, direção, <span style="color:#66d9ef">False</span>)
</span></span><span style="display:flex;"><span> pode_empurrar2 <span style="color:#f92672">=</span> super()<span style="color:#f92672">.</span>empurra(caixa2, direção, <span style="color:#66d9ef">False</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> pode_empurrar1 <span style="color:#f92672">and</span> pode_empurrar2:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> executa:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>empurra_v(caixa1, caixa2, direção)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">True</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">False</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">ache_caixa</span>(self, posição):
</span></span><span style="display:flex;"><span> caixa1 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>caixas[posição]
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> caixa1<span style="color:#f92672">.</span>símbolo <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;[&#34;</span>:
</span></span><span style="display:flex;"><span> caixa2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>caixas[posição <span style="color:#f92672">+</span> Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#ae81ff">1</span>)]
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">else</span>:
</span></span><span style="display:flex;"><span> caixa2 <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>caixas[posição <span style="color:#f92672">+</span> Vector2d(<span style="color:#ae81ff">0</span>, <span style="color:#f92672">-</span><span style="color:#ae81ff">1</span>)]
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> caixa1, caixa2
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">empurra_v</span>(self, posição1, posição2, direção):
</span></span><span style="display:flex;"><span> np1 <span style="color:#f92672">=</span> posição1 <span style="color:#f92672">+</span> direção
</span></span><span style="display:flex;"><span> np2 <span style="color:#f92672">=</span> posição2 <span style="color:#f92672">+</span> direção
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> np1 <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>caixas:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>empurra_v(<span style="color:#f92672">*</span>self<span style="color:#f92672">.</span>ache_caixa(np1), direção)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> np2 <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>caixas:
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>empurra_v(<span style="color:#f92672">*</span>self<span style="color:#f92672">.</span>ache_caixa(np2), direção)
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> posição, nova_posição <span style="color:#f92672">in</span> zip([posição1, posição2], [np1, np2]):
</span></span><span style="display:flex;"><span> caixa <span style="color:#f92672">=</span> self<span style="color:#f92672">.</span>caixas<span style="color:#f92672">.</span>pop(posição)
</span></span><span style="display:flex;"><span> caixa<span style="color:#f92672">.</span>y <span style="color:#f92672">=</span> nova_posição<span style="color:#f92672">.</span>y
</span></span><span style="display:flex;"><span> caixa<span style="color:#f92672">.</span>x <span style="color:#f92672">=</span> nova_posição<span style="color:#f92672">.</span>x
</span></span><span style="display:flex;"><span> self<span style="color:#f92672">.</span>caixas[caixa] <span style="color:#f92672">=</span> caixa
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">def</span> <span style="color:#a6e22e">gps_caixas</span>(self):
</span></span><span style="display:flex;"><span> soma <span style="color:#f92672">=</span> <span style="color:#ae81ff">0</span>
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">for</span> caixa <span style="color:#f92672">in</span> self<span style="color:#f92672">.</span>caixas:
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">if</span> caixa<span style="color:#f92672">.</span>símbolo <span style="color:#f92672">==</span> <span style="color:#e6db74">&#34;[&#34;</span>:
</span></span><span style="display:flex;"><span> soma <span style="color:#f92672">+=</span> caixa<span style="color:#f92672">.</span>y <span style="color:#f92672">*</span> <span style="color:#ae81ff">100</span> <span style="color:#f92672">+</span> caixa<span style="color:#f92672">.</span>x
</span></span><span style="display:flex;"><span> <span style="color:#66d9ef">return</span> soma
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problema <span style="color:#f92672">=</span> Problema(<span style="color:#f92672">*</span>le_entrada(ENTRADA_TESTE))
</span></span><span style="display:flex;"><span>problema<span style="color:#f92672">.</span>executa_comandos()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problema<span style="color:#f92672">.</span>gps_caixas()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Parte 1 - teste pequena entrada:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">2028</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problema <span style="color:#f92672">=</span> Problema(<span style="color:#f92672">*</span>le_entrada(ENTRADA))
</span></span><span style="display:flex;"><span>problema<span style="color:#f92672">.</span>executa_comandos()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problema<span style="color:#f92672">.</span>gps_caixas()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Parte 1 - teste entrada grande:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">10092</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problema <span style="color:#f92672">=</span> Problema(<span style="color:#f92672">*</span>le_entrada(le_arquivo_sem_linhas_em_branco(<span style="color:#e6db74">&#34;15/input.txt&#34;</span>)))
</span></span><span style="display:flex;"><span>problema<span style="color:#f92672">.</span>executa_comandos()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problema<span style="color:#f92672">.</span>gps_caixas()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Parte 1 - entrada grande:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">1552463</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#75715e"># Parte 2</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problema <span style="color:#f92672">=</span> Problema2(<span style="color:#f92672">*</span>le_entrada(ENTRADA))
</span></span><span style="display:flex;"><span>problema<span style="color:#f92672">.</span>executa_comandos()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problema<span style="color:#f92672">.</span>gps_caixas()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Parte 2 - teste entrada grande:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">9021</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>problema <span style="color:#f92672">=</span> Problema2(<span style="color:#f92672">*</span>le_entrada(le_arquivo_sem_linhas_em_branco(<span style="color:#e6db74">&#34;15/input.txt&#34;</span>)))
</span></span><span style="display:flex;"><span>problema<span style="color:#f92672">.</span>executa_comandos()
</span></span><span style="display:flex;"><span>gps <span style="color:#f92672">=</span> problema<span style="color:#f92672">.</span>gps_caixas()
</span></span><span style="display:flex;"><span>print(<span style="color:#e6db74">&#34;Parte 2 - entrada grande:&#34;</span>, gps)
</span></span><span style="display:flex;"><span><span style="color:#66d9ef">assert</span> gps <span style="color:#f92672">==</span> <span style="color:#ae81ff">1554058</span>
</span></span></code></pre></div>
2024-12-16T00:49:05+00:00
-
Rodrigo Delduca: My First Game with Carimbo, My Homemade Engine, For my Son
https://nullonerror.org/2024/10/08/my-first-game-with-carimbo/
<p><a href="https://youtu.be/nVRzCstyspQ"><img src="https://nullonerror.org/public/2024-10-08-my-first-game-with-carimbo/megarick.webp" alt="MegaRick Game" class="center" /></a>
<a href="https://youtu.be/nVRzCstyspQ">https://youtu.be/nVRzCstyspQ</a></p>
<h3 id="tldr">TL;DR</h3>
<p>After a while, I decided to resume work on <a href="https://github.com/carimbolabs/carimbo">my game engine</a>.</p>
<p>I made a game for my son. I could have used an existing engine, but I chose to write everything from scratch because code is like pasta—it’s much better and more enjoyable when homemade.</p>
<p>This actually reminds me of when my father used to build my toys, from kites to wooden slides. Good memories. I have decided to do the same using what I know: programming.</p>
<p>You can watch it here or <a href="https://carimbo.run">play it here</a>, (runs in the browser thanks to WebAssembly), use <code class="language-plaintext highlighter-rouge">A</code> and <code class="language-plaintext highlighter-rouge">D</code> for moving around and <code class="language-plaintext highlighter-rouge">space</code> to shoot.</p>
<p>The engine was written in C++17, and the games are in Lua. The engine exposes some primitives to the Lua VM, which in turn coordinates the entire game.</p>
<p><a href="https://github.com/willtobyte/megarick">Game source code</a> and <a href="https://github.com/willtobyte/carimbo">engine source code</a>.</p>
<p>Artwork by <a href="https://www.fiverr.com/yuugenpixie">Aline Cardoso @yuugenpixie</a>.</p>
<h3 id="result">Result</h3>
<p><img src="https://nullonerror.org/public/2024-10-08-my-first-game-with-carimbo/play.jpeg" alt="Henrique Playing" class="center" /></p>
<h3 id="going-deep">Going Deep</h3>
<p>The entire game is scripted in <a href="https://www.lua.org">Lua</a>.</p>
<p>First, we create the engine itself.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">engine</span> <span class="o">=</span> <span class="n">EngineFactory</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="p">:</span><span class="n">set_title</span><span class="p">(</span><span class="s2">"Mega Rick"</span><span class="p">)</span>
<span class="p">:</span><span class="n">set_width</span><span class="p">(</span><span class="mi">1920</span><span class="p">)</span>
<span class="p">:</span><span class="n">set_height</span><span class="p">(</span><span class="mi">1080</span><span class="p">)</span>
<span class="p">:</span><span class="n">set_fullscreen</span><span class="p">(</span><span class="kc">false</span><span class="p">)</span>
<span class="p">:</span><span class="n">create</span><span class="p">()</span>
</code></pre></div></div>
<p>Then we point to some resources for asset prefetching; they are loaded lazily to avoid impacting the <em>game loop</em>.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">engine</span><span class="p">:</span><span class="n">prefetch</span><span class="p">({</span>
<span class="s2">"blobs/bomb1.ogg"</span><span class="p">,</span>
<span class="s2">"blobs/bomb2.ogg"</span><span class="p">,</span>
<span class="s2">"blobs/bullet.png"</span><span class="p">,</span>
<span class="s2">"blobs/candle.png"</span><span class="p">,</span>
<span class="s2">"blobs/explosion.png"</span><span class="p">,</span>
<span class="s2">"blobs/octopus.png"</span><span class="p">,</span>
<span class="s2">"blobs/player.png"</span><span class="p">,</span>
<span class="s2">"blobs/princess.png"</span><span class="p">,</span>
<span class="s2">"blobs/ship.png"</span>
<span class="p">})</span>
</code></pre></div></div>
<p>We created the postal service, which is something I borrowed from languages like <em>Erlang</em>, where an entity can send messages to any other. As we’ll see below, the bullet sends a hit message when it collides with the octopus, and the octopus, upon receiving the hit, decrements its own health.</p>
<p>We also obtain the SoundManager to play sounds and spawn the main entities.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">local</span> <span class="n">postal</span> <span class="o">=</span> <span class="n">PostalService</span><span class="p">.</span><span class="n">new</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">soundmanager</span> <span class="o">=</span> <span class="n">engine</span><span class="p">:</span><span class="n">soundmanager</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">octopus</span> <span class="o">=</span> <span class="n">engine</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="s2">"octopus"</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">player</span> <span class="o">=</span> <span class="n">engine</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="s2">"player"</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">princess</span> <span class="o">=</span> <span class="n">engine</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="s2">"princess"</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">candle1</span> <span class="o">=</span> <span class="n">engine</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="s2">"candle"</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">candle2</span> <span class="o">=</span> <span class="n">engine</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="s2">"candle"</span><span class="p">)</span>
</code></pre></div></div>
<p>It’s not a triple-A title, but I’m very careful with resource management. A widely known technique is to create an object pool that you can reuse. In the code below, we limit it to a maximum of 3 bullets present on the screen.</p>
<p>The <code class="language-plaintext highlighter-rouge">on_update</code> method is a callback called each loop in the engine. In the case of the bullet, I check if it has approached the green octopus. If so, I send a message to it indicating that it received a hit.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">for</span> <span class="n">_</span> <span class="o">=</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">3</span> <span class="k">do</span>
<span class="kd">local</span> <span class="n">bullet</span> <span class="o">=</span> <span class="n">entitymanager</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="s2">"bullet"</span><span class="p">)</span>
<span class="n">bullet</span><span class="p">.</span><span class="n">placement</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="o">-</span><span class="mi">128</span><span class="p">,</span> <span class="o">-</span><span class="mi">128</span><span class="p">)</span>
<span class="n">bullet</span><span class="p">:</span><span class="n">on_collision</span><span class="p">(</span><span class="s2">"octopus"</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">action</span><span class="p">:</span><span class="n">unset</span><span class="p">()</span>
<span class="n">self</span><span class="p">.</span><span class="n">placement</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="o">-</span><span class="mi">128</span><span class="p">,</span> <span class="o">-</span><span class="mi">128</span><span class="p">)</span>
<span class="n">postalservice</span><span class="p">:</span><span class="n">post</span><span class="p">(</span><span class="n">Mail</span><span class="p">.</span><span class="n">new</span><span class="p">(</span><span class="n">octopus</span><span class="p">,</span> <span class="s2">"bullet"</span><span class="p">,</span> <span class="s2">"hit"</span><span class="p">))</span>
<span class="nb">table.insert</span><span class="p">(</span><span class="n">bullet_pool</span><span class="p">,</span> <span class="n">self</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
<span class="nb">table.insert</span><span class="p">(</span><span class="n">bullet_pool</span><span class="p">,</span> <span class="n">bullet</span><span class="p">)</span>
<span class="k">end</span>
</code></pre></div></div>
<p>On the octopus’s side, when it receives a “hit” message, the entity triggers an explosion animation, switches to attack mode, and decreases its health by 1. If it reaches 0, it changes the animation to “dead”.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">octopus</span> <span class="o">=</span> <span class="n">entitymanager</span><span class="p">:</span><span class="n">spawn</span><span class="p">(</span><span class="s2">"octopus"</span><span class="p">)</span>
<span class="n">octopus</span><span class="p">.</span><span class="n">kv</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="s2">"life"</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span>
<span class="n">octopus</span><span class="p">.</span><span class="n">placement</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="mi">1200</span><span class="p">,</span> <span class="mi">622</span><span class="p">)</span>
<span class="n">octopus</span><span class="p">.</span><span class="n">action</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="s2">"idle"</span><span class="p">)</span>
<span class="n">octopus</span><span class="p">:</span><span class="n">on_mail</span><span class="p">(</span><span class="k">function</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">behavior</span> <span class="o">=</span> <span class="n">behaviors</span><span class="p">[</span><span class="n">message</span><span class="p">]</span>
<span class="k">if</span> <span class="n">behavior</span> <span class="k">then</span>
<span class="n">behavior</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">octopus</span><span class="p">.</span><span class="n">kv</span><span class="p">:</span><span class="n">subscribe</span><span class="p">(</span><span class="s2">"life"</span><span class="p">,</span> <span class="k">function</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>
<span class="n">vitality</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="nb">string.format</span><span class="p">(</span><span class="s2">"%02d-"</span><span class="p">,</span> <span class="nb">math.max</span><span class="p">(</span><span class="n">value</span><span class="p">,</span> <span class="mi">0</span><span class="p">)))</span>
<span class="k">if</span> <span class="n">value</span> <span class="o"><=</span> <span class="mi">0</span> <span class="k">then</span>
<span class="n">octopus</span><span class="p">.</span><span class="n">action</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="s2">"dead"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">timer</span> <span class="k">then</span>
<span class="n">timemanager</span><span class="p">:</span><span class="n">singleshot</span><span class="p">(</span><span class="mi">3000</span><span class="p">,</span> <span class="k">function</span><span class="p">()</span>
<span class="kd">local</span> <span class="k">function</span> <span class="nf">destroy</span><span class="p">(</span><span class="n">pool</span><span class="p">)</span>
<span class="k">for</span> <span class="n">i</span> <span class="o">=</span> <span class="o">#</span><span class="n">pool</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span> <span class="k">do</span>
<span class="n">entitymanager</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">pool</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="nb">table.remove</span><span class="p">(</span><span class="n">pool</span><span class="p">,</span> <span class="n">i</span><span class="p">)</span>
<span class="n">pool</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="n">destroy</span><span class="p">(</span><span class="n">bullet_pool</span><span class="p">)</span>
<span class="n">destroy</span><span class="p">(</span><span class="n">explosion_pool</span><span class="p">)</span>
<span class="n">destroy</span><span class="p">(</span><span class="n">jet_pool</span><span class="p">)</span>
<span class="n">entitymanager</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">octopus</span><span class="p">)</span>
<span class="n">octopus</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">entitymanager</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">player</span><span class="p">)</span>
<span class="n">player</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">entitymanager</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">princess</span><span class="p">)</span>
<span class="n">princess</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">entitymanager</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">candle1</span><span class="p">)</span>
<span class="n">candle1</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">entitymanager</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">candle2</span><span class="p">)</span>
<span class="n">candle2</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">entitymanager</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">floor</span><span class="p">)</span>
<span class="n">floor</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">overlay</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">vitality</span><span class="p">)</span>
<span class="n">vitality</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">overlay</span><span class="p">:</span><span class="n">destroy</span><span class="p">(</span><span class="n">online</span><span class="p">)</span>
<span class="n">online</span> <span class="o">=</span> <span class="kc">nil</span>
<span class="n">scenemanager</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="s2">"gameover"</span><span class="p">)</span>
<span class="nb">collectgarbage</span><span class="p">(</span><span class="s2">"collect"</span><span class="p">)</span>
<span class="n">resourcemanager</span><span class="p">:</span><span class="n">flush</span><span class="p">()</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">timer</span> <span class="o">=</span> <span class="kc">true</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">end</span><span class="p">)</span>
<span class="n">octopus</span><span class="p">:</span><span class="n">on_animationfinished</span><span class="p">(</span><span class="k">function</span><span class="p">(</span><span class="n">self</span><span class="p">)</span>
<span class="n">self</span><span class="p">.</span><span class="n">action</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="s2">"idle"</span><span class="p">)</span>
<span class="k">end</span><span class="p">)</span>
</code></pre></div></div>
<p>And finally, the player’s <code class="language-plaintext highlighter-rouge">on_update</code>, where I apply the velocity if any movement keys are pressed, or set the animation to idle if none are pressed.</p>
<p>We also have the projectile firing, where the fire function is called, taking an instance of a bullet from the pool, placing it in a semi-random position, and applying velocity.</p>
<div class="language-lua highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">function</span> <span class="nf">loop</span><span class="p">()</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">player</span> <span class="k">then</span>
<span class="k">return</span>
<span class="k">end</span>
<span class="n">player</span><span class="p">.</span><span class="n">velocity</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">if</span> <span class="n">statemanager</span><span class="p">:</span><span class="n">is_keydown</span><span class="p">(</span><span class="n">KeyEvent</span><span class="p">.</span><span class="n">left</span><span class="p">)</span> <span class="k">then</span>
<span class="n">player</span><span class="p">.</span><span class="n">reflection</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="n">Reflection</span><span class="p">.</span><span class="n">horizontal</span><span class="p">)</span>
<span class="n">player</span><span class="p">.</span><span class="n">velocity</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="o">-</span><span class="mi">360</span>
<span class="k">elseif</span> <span class="n">statemanager</span><span class="p">:</span><span class="n">is_keydown</span><span class="p">(</span><span class="n">KeyEvent</span><span class="p">.</span><span class="n">right</span><span class="p">)</span> <span class="k">then</span>
<span class="n">player</span><span class="p">.</span><span class="n">reflection</span><span class="p">:</span><span class="n">unset</span><span class="p">()</span>
<span class="n">player</span><span class="p">.</span><span class="n">velocity</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">360</span>
<span class="k">end</span>
<span class="n">player</span><span class="p">.</span><span class="n">action</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="n">player</span><span class="p">.</span><span class="n">velocity</span><span class="p">.</span><span class="n">x</span> <span class="o">~=</span> <span class="mi">0</span> <span class="ow">and</span> <span class="s2">"run"</span> <span class="ow">or</span> <span class="s2">"idle"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">statemanager</span><span class="p">:</span><span class="n">is_keydown</span><span class="p">(</span><span class="n">KeyEvent</span><span class="p">.</span><span class="n">space</span><span class="p">)</span> <span class="k">then</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">key_states</span><span class="p">[</span><span class="n">KeyEvent</span><span class="p">.</span><span class="n">space</span><span class="p">]</span> <span class="k">then</span>
<span class="n">key_states</span><span class="p">[</span><span class="n">KeyEvent</span><span class="p">.</span><span class="n">space</span><span class="p">]</span> <span class="o">=</span> <span class="kc">true</span>
<span class="c1">-- player.velocity.y = -360</span>
<span class="k">if</span> <span class="n">octopus</span><span class="p">.</span><span class="n">kv</span><span class="p">:</span><span class="n">get</span><span class="p">(</span><span class="s2">"life"</span><span class="p">)</span> <span class="o"><=</span> <span class="mi">0</span> <span class="k">then</span>
<span class="k">return</span>
<span class="k">end</span>
<span class="k">if</span> <span class="o">#</span><span class="n">bullet_pool</span> <span class="o">></span> <span class="mi">0</span> <span class="k">then</span>
<span class="kd">local</span> <span class="n">bullet</span> <span class="o">=</span> <span class="nb">table.remove</span><span class="p">(</span><span class="n">bullet_pool</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">x</span> <span class="o">=</span> <span class="p">(</span><span class="n">player</span><span class="p">.</span><span class="n">x</span> <span class="o">+</span> <span class="n">player</span><span class="p">.</span><span class="n">size</span><span class="p">.</span><span class="n">width</span><span class="p">)</span> <span class="o">+</span> <span class="mi">100</span>
<span class="kd">local</span> <span class="n">y</span> <span class="o">=</span> <span class="n">player</span><span class="p">.</span><span class="n">y</span> <span class="o">+</span> <span class="mi">10</span>
<span class="kd">local</span> <span class="n">offset_y</span> <span class="o">=</span> <span class="p">(</span><span class="nb">math.random</span><span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">))</span> <span class="o">*</span> <span class="mi">30</span>
<span class="n">bullet</span><span class="p">.</span><span class="n">placement</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span> <span class="o">+</span> <span class="n">offset_y</span><span class="p">)</span>
<span class="n">bullet</span><span class="p">.</span><span class="n">action</span><span class="p">:</span><span class="n">set</span><span class="p">(</span><span class="s2">"default"</span><span class="p">)</span>
<span class="n">bullet</span><span class="p">.</span><span class="n">velocity</span><span class="p">.</span><span class="n">x</span> <span class="o">=</span> <span class="mi">800</span>
<span class="kd">local</span> <span class="n">sound</span> <span class="o">=</span> <span class="s2">"bomb"</span> <span class="o">..</span> <span class="nb">math.random</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="n">soundmanager</span><span class="p">:</span><span class="n">play</span><span class="p">(</span><span class="n">sound</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="k">else</span>
<span class="n">key_states</span><span class="p">[</span><span class="n">KeyEvent</span><span class="p">.</span><span class="n">space</span><span class="p">]</span> <span class="o">=</span> <span class="kc">false</span>
<span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>
<h3 id="in-the-end">In The End</h3>
<p>In the end, he requested some <em>minor</em> tweaks, such as the projectile being the princess sprite, the target being the player, and the entity that shoots being the green octopus. 🤷♂️</p>
<p><img src="https://nullonerror.org/public/2024-10-08-my-first-game-with-carimbo/tweaks.jpeg" alt="Tweaks" class="center" /></p>
2024-10-08T00:00:00+00:00
Rodrigo Delduca
-
Rodrigo Delduca: How I saved a few dozen dollars by migrating my personal projects from the cloud to a Raspberry Pi
https://nullonerror.org/2024/06/05/how-i-saved-a-few-dozen-dollars-by-migrating-my-personal-projects-from-the-cloud-to-a-raspberry-pi/
<h3 id="intro">Intro</h3>
<p>Since the first URL shorteners emerged, that’s always drawn me in for some reason, perhaps because I figured out on my own that they encoded the primary key in base36 (maybe).</p>
<p>So around ~2010, I decided to make my own. It was called “encurta.ae” (make it short there in Portuguese), and it was based on AppEngine. Even back then, I was quite fond of serverless and such.</p>
<p>It worked great locally, but when I deployed it, the datastore IDs were too large, causing the URLs to be too long.</p>
<p>Fast-forward to nowadays, I’ve decided to bring this project to life again. This time with a twist: when shortening the URL, the shortener would take a screenshot of the website and embed Open Graph tags in the redirect URL. This way, when shared on a social network, the link would display a title, description, and thumbnail that accurately represent what the user is about to open.</p>
<h3 id="stack">Stack</h3>
<p>Typically, I use serverless for my personal projects because they scale to zero, thus eliminating costs when not in use. However, after working with serverless for many years, I’ve been wanting to experiment with a more grounded and down-to-earth approach to development and deployment.</p>
<ul>
<li>I opted to use <a href="https://go.dev/">Go</a>, with several <a href="https://en.wikipedia.org/wiki/Coroutine">goroutines</a> performing tasks in parallel, and a purely written work queue mechanism.</li>
<li><a href="https://playwright.dev/">Playwright</a> & Chromium for automation and screenshot.</li>
<li><a href="https://sqlite.org/">SQLite</a> for the database (it’s simple)</li>
<li><a href="https://www.backblaze.com/">Backblaze</a> for storage</li>
<li><a href="https://bunny.net/">BunnyCDN</a> for cotent delivery</li>
<li><a href="https://www.papertrail.com/">Papertrail</a> for logging</li>
</ul>
<p>Deployment is done using Docker Compose, via SSH using the DOCKER_HOST environment variable pointing directly to a <a href="https://www.raspberrypi.com/">Raspberry Pi</a> that I had bought and never used before. Now it saves me $5 per month, and I can keep a limited number of projects running on it.</p>
<p>And then you might ask: How do you expose it to the internet? I use <a href="https://www.cloudflare.com/products/tunnel/">Cloudflare Tunnel</a>; the setup is simple and creates a direct connection between the Raspberry Pi and the nearest <a href="https://en.wikipedia.org/wiki/Point_of_presence">Point of Presence</a>.</p>
<p>This type of hosting is extremely advantageous because the server’s IP and/or ports are never revealed; it stays behind my firewall. Everything goes through Cloudflare.</p>
<p>I have more than one Docker Compose file, and that’s what’s coolest. Locally, I run one, for deployment I instruct the Compose to read two others for logging and tunneling.</p>
<p><code class="language-plaintext highlighter-rouge">docker-compose.yaml</code></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span>
<span class="na">build</span><span class="pi">:</span> <span class="s">.</span>
<span class="na">env_file</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">.env</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">8000:8000"</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">data:/data</span>
<span class="na">tmpfs</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">/tmp</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="na">data</span><span class="pi">:</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">docker-compose.logging.yaml</code></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
<span class="na">app</span><span class="pi">:</span>
<span class="na">logging</span><span class="pi">:</span>
<span class="na">driver</span><span class="pi">:</span> <span class="s">syslog</span>
<span class="na">options</span><span class="pi">:</span>
<span class="na">syslog-address</span><span class="pi">:</span> <span class="s2">"</span><span class="s">udp://logs2.papertrailapp.com:XXX"</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">docker-compose.cloudflare.yaml</code></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">services</span><span class="pi">:</span>
<span class="na">tunnel</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">cloudflare/cloudflared</span>
<span class="na">restart</span><span class="pi">:</span> <span class="s">unless-stopped</span>
<span class="na">command</span><span class="pi">:</span> <span class="s">tunnel run</span>
<span class="na">environment</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">TUNNEL_TOKEN=yourtoken</span>
</code></pre></div></div>
<p>Then for deploying</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">DOCKER_HOST</span><span class="o">=</span>ssh://pi@192.168.0.10 docker compose <span class="nt">--file</span> docker-compose.yaml <span class="nt">--file</span> docker-compose.logging.yaml <span class="nt">--file</span> docker-compose.cloudflare.yaml up <span class="nt">--build</span> <span class="nt">--detach</span>
</code></pre></div></div>
<p>Example of the “worker queue”</p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">functions</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"context"</span>
<span class="s">"database/sql"</span>
<span class="s">"fmt"</span>
<span class="s">"os"</span>
<span class="s">"os/exec"</span>
<span class="s">"path/filepath"</span>
<span class="s">"sync"</span>
<span class="s">"time"</span>
<span class="n">google</span> <span class="s">"cloud.google.com/go/vision/apiv1"</span>
<span class="n">visionpb</span> <span class="s">"cloud.google.com/go/vision/v2/apiv1/visionpb"</span>
<span class="s">"github.com/martinlindhe/base36"</span>
<span class="s">"github.com/minio/minio-go/v7"</span>
<span class="s">"github.com/minio/minio-go/v7/pkg/credentials"</span>
<span class="s">"github.com/playwright-community/playwright-go"</span>
<span class="s">"go.uber.org/zap"</span>
<span class="s">"google.golang.org/api/option"</span>
<span class="n">log</span> <span class="s">"skhaz.dev/urlshortnen/logging"</span>
<span class="p">)</span>
<span class="k">var</span> <span class="p">(</span>
<span class="n">accessKeyID</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"BACKBLAZE_ACCESS_ID"</span><span class="p">)</span>
<span class="n">secretAccessKey</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"BACKBLAZE_APPLICATION_KEY"</span><span class="p">)</span>
<span class="n">bucket</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"BACKBLAZE_BUCKET"</span><span class="p">)</span>
<span class="n">endpoint</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"BACKBLAZE_ENDPOINT"</span><span class="p">)</span>
<span class="n">useSSL</span> <span class="o">=</span> <span class="no">true</span>
<span class="n">extension</span> <span class="o">=</span> <span class="s">"webp"</span>
<span class="n">mimetype</span> <span class="o">=</span> <span class="s">"image/webp"</span>
<span class="n">quality</span> <span class="o">=</span> <span class="s">"50"</span>
<span class="p">)</span>
<span class="k">type</span> <span class="n">WorkerFunctions</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">db</span> <span class="o">*</span><span class="n">sql</span><span class="o">.</span><span class="n">DB</span>
<span class="n">vision</span> <span class="o">*</span><span class="n">google</span><span class="o">.</span><span class="n">ImageAnnotatorClient</span>
<span class="n">mc</span> <span class="o">*</span><span class="n">minio</span><span class="o">.</span><span class="n">Client</span>
<span class="n">browser</span> <span class="n">playwright</span><span class="o">.</span><span class="n">BrowserContext</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">Worker</span><span class="p">(</span><span class="n">db</span> <span class="o">*</span><span class="n">sql</span><span class="o">.</span><span class="n">DB</span><span class="p">)</span> <span class="p">{</span>
<span class="k">defer</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">r</span> <span class="o">:=</span> <span class="nb">recover</span><span class="p">();</span> <span class="n">r</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"worker panic"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Any</span><span class="p">(</span><span class="s">"error"</span><span class="p">,</span> <span class="n">r</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="n">time</span><span class="o">.</span><span class="n">Second</span> <span class="o">*</span> <span class="m">10</span><span class="p">)</span>
<span class="k">go</span> <span class="n">Worker</span><span class="p">(</span><span class="n">db</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}()</span>
<span class="n">ctx</span> <span class="o">:=</span> <span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">()</span>
<span class="n">vision</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">google</span><span class="o">.</span><span class="n">NewImageAnnotatorClient</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">option</span><span class="o">.</span><span class="n">WithCredentialsJSON</span><span class="p">([]</span><span class="kt">byte</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"GOOGLE_CREDENTIALS"</span><span class="p">))))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"failed to create vision client"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">vision</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="n">mc</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">minio</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">endpoint</span><span class="p">,</span> <span class="o">&</span><span class="n">minio</span><span class="o">.</span><span class="n">Options</span><span class="p">{</span>
<span class="n">Creds</span><span class="o">:</span> <span class="n">credentials</span><span class="o">.</span><span class="n">NewStaticV4</span><span class="p">(</span><span class="n">accessKeyID</span><span class="p">,</span> <span class="n">secretAccessKey</span><span class="p">,</span> <span class="s">""</span><span class="p">),</span>
<span class="n">Secure</span><span class="o">:</span> <span class="n">useSSL</span><span class="p">,</span>
<span class="p">})</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"failed to create minio client"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">pw</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">playwright</span><span class="o">.</span><span class="n">Run</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"failed to launch playwright"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="c">//nolint:golint,errcheck</span>
<span class="k">defer</span> <span class="n">pw</span><span class="o">.</span><span class="n">Stop</span><span class="p">()</span>
<span class="n">userDataDir</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">MkdirTemp</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="s">"chromium"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"failed to create temporary directory"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">os</span><span class="o">.</span><span class="n">RemoveAll</span><span class="p">(</span><span class="n">userDataDir</span><span class="p">)</span>
<span class="n">browser</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">pw</span><span class="o">.</span><span class="n">Chromium</span><span class="o">.</span><span class="n">LaunchPersistentContext</span><span class="p">(</span><span class="n">userDataDir</span><span class="p">,</span> <span class="n">playwright</span><span class="o">.</span><span class="n">BrowserTypeLaunchPersistentContextOptions</span><span class="p">{</span>
<span class="n">Args</span><span class="o">:</span> <span class="p">[]</span><span class="kt">string</span><span class="p">{</span>
<span class="s">"--headless=new"</span><span class="p">,</span>
<span class="s">"--no-zygote"</span><span class="p">,</span>
<span class="s">"--no-sandbox"</span><span class="p">,</span>
<span class="s">"--disable-gpu"</span><span class="p">,</span>
<span class="s">"--hide-scrollbars"</span><span class="p">,</span>
<span class="s">"--disable-setuid-sandbox"</span><span class="p">,</span>
<span class="s">"--disable-dev-shm-usage"</span><span class="p">,</span>
<span class="s">"--disable-extensions-except=/opt/extensions/ublock,/opt/extensions/isdncac"</span><span class="p">,</span>
<span class="s">"--load-extension=/opt/extensions/ublock,/opt/extensions/isdncac"</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">DeviceScaleFactor</span><span class="o">:</span> <span class="n">playwright</span><span class="o">.</span><span class="n">Float</span><span class="p">(</span><span class="m">4.0</span><span class="p">),</span>
<span class="n">Headless</span><span class="o">:</span> <span class="n">playwright</span><span class="o">.</span><span class="n">Bool</span><span class="p">(</span><span class="no">false</span><span class="p">),</span>
<span class="n">Viewport</span><span class="o">:</span> <span class="o">&</span><span class="n">playwright</span><span class="o">.</span><span class="n">Size</span><span class="p">{</span>
<span class="n">Width</span><span class="o">:</span> <span class="m">1200</span><span class="p">,</span>
<span class="n">Height</span><span class="o">:</span> <span class="m">630</span><span class="p">,</span>
<span class="p">},</span>
<span class="n">UserAgent</span><span class="o">:</span> <span class="n">playwright</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="s">"Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; Googlebot/2.1; +https://www.google.com/bot.html) Chrome/125.0.0.0 Safari/537.36"</span><span class="p">),</span>
<span class="p">})</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"failed to launch chromium browser"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">browser</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="n">wf</span> <span class="o">:=</span> <span class="n">WorkerFunctions</span><span class="p">{</span>
<span class="n">db</span><span class="o">:</span> <span class="n">db</span><span class="p">,</span>
<span class="n">vision</span><span class="o">:</span> <span class="n">vision</span><span class="p">,</span>
<span class="n">mc</span><span class="o">:</span> <span class="n">mc</span><span class="p">,</span>
<span class="n">browser</span><span class="o">:</span> <span class="n">browser</span><span class="p">,</span>
<span class="p">}</span>
<span class="k">for</span> <span class="p">{</span>
<span class="n">start</span> <span class="o">:=</span> <span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">()</span>
<span class="k">func</span><span class="p">()</span> <span class="p">{</span>
<span class="k">var</span> <span class="p">(</span>
<span class="n">wg</span> <span class="n">sync</span><span class="o">.</span><span class="n">WaitGroup</span>
<span class="n">rows</span> <span class="o">*</span><span class="n">sql</span><span class="o">.</span><span class="n">Rows</span>
<span class="n">err</span> <span class="kt">error</span>
<span class="p">)</span>
<span class="n">rows</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">db</span><span class="o">.</span><span class="n">Query</span><span class="p">(</span><span class="s">"SELECT id, url FROM data WHERE ready = 0 ORDER BY created_at LIMIT 6"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"error executing query"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">rows</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="k">for</span> <span class="n">rows</span><span class="o">.</span><span class="n">Next</span><span class="p">()</span> <span class="p">{</span>
<span class="k">var</span> <span class="n">id</span> <span class="kt">int64</span>
<span class="k">var</span> <span class="n">url</span> <span class="kt">string</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">=</span> <span class="n">rows</span><span class="o">.</span><span class="n">Scan</span><span class="p">(</span><span class="o">&</span><span class="n">id</span><span class="p">,</span> <span class="o">&</span><span class="n">url</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"error scanning row"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">wg</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="m">1</span><span class="p">)</span>
<span class="k">go</span> <span class="n">wf</span><span class="o">.</span><span class="n">run</span><span class="p">(</span><span class="o">&</span><span class="n">wg</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">id</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">rows</span><span class="o">.</span><span class="n">Err</span><span class="p">();</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"error during rows iteration"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">wg</span><span class="o">.</span><span class="n">Wait</span><span class="p">()</span>
<span class="p">}()</span>
<span class="n">elapsed</span> <span class="o">:=</span> <span class="n">time</span><span class="o">.</span><span class="n">Since</span><span class="p">(</span><span class="n">start</span><span class="p">)</span>
<span class="k">if</span> <span class="n">remaining</span> <span class="o">:=</span> <span class="m">5</span><span class="o">*</span><span class="n">time</span><span class="o">.</span><span class="n">Second</span> <span class="o">-</span> <span class="n">elapsed</span><span class="p">;</span> <span class="n">remaining</span> <span class="o">></span> <span class="m">0</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="n">remaining</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">func</span> <span class="p">(</span><span class="n">wf</span> <span class="o">*</span><span class="n">WorkerFunctions</span><span class="p">)</span> <span class="n">run</span><span class="p">(</span><span class="n">wg</span> <span class="o">*</span><span class="n">sync</span><span class="o">.</span><span class="n">WaitGroup</span><span class="p">,</span> <span class="n">url</span> <span class="kt">string</span><span class="p">,</span> <span class="n">id</span> <span class="kt">int64</span><span class="p">)</span> <span class="p">{</span>
<span class="k">defer</span> <span class="n">wg</span><span class="o">.</span><span class="n">Done</span><span class="p">()</span>
<span class="k">var</span> <span class="n">message</span> <span class="kt">string</span>
<span class="k">if</span> <span class="n">id</span> <span class="o"><</span> <span class="m">0</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="s">"invalid id: id must be non-negative"</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">var</span> <span class="p">(</span>
<span class="n">ctx</span> <span class="o">=</span> <span class="n">context</span><span class="o">.</span><span class="n">Background</span><span class="p">()</span>
<span class="n">short</span> <span class="o">=</span> <span class="n">base36</span><span class="o">.</span><span class="n">Encode</span><span class="p">(</span><span class="kt">uint64</span><span class="p">(</span><span class="n">id</span><span class="p">))</span>
<span class="p">)</span>
<span class="n">dir</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">MkdirTemp</span><span class="p">(</span><span class="s">""</span><span class="p">,</span> <span class="s">"screenshot"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to create temporary directory: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">os</span><span class="o">.</span><span class="n">RemoveAll</span><span class="p">(</span><span class="n">dir</span><span class="p">)</span>
<span class="k">var</span> <span class="p">(</span>
<span class="n">fileName</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"%s.%s"</span><span class="p">,</span> <span class="n">short</span><span class="p">,</span> <span class="n">extension</span><span class="p">)</span>
<span class="n">filePath</span> <span class="o">=</span> <span class="n">filepath</span><span class="o">.</span><span class="n">Join</span><span class="p">(</span><span class="n">dir</span><span class="p">,</span> <span class="n">fileName</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">page</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">wf</span><span class="o">.</span><span class="n">browser</span><span class="o">.</span><span class="n">NewPage</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to create new page: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">page</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">page</span><span class="o">.</span><span class="n">Goto</span><span class="p">(</span><span class="n">url</span><span class="p">,</span> <span class="n">playwright</span><span class="o">.</span><span class="n">PageGotoOptions</span><span class="p">{</span>
<span class="n">WaitUntil</span><span class="o">:</span> <span class="n">playwright</span><span class="o">.</span><span class="n">WaitUntilStateDomcontentloaded</span><span class="p">,</span>
<span class="p">});</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to navigate to url: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</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="n">time</span><span class="o">.</span><span class="n">Second</span> <span class="o">*</span> <span class="m">5</span><span class="p">)</span>
<span class="n">title</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">page</span><span class="o">.</span><span class="n">Title</span><span class="p">()</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"could not get title: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">description</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">page</span><span class="o">.</span><span class="n">Locator</span><span class="p">(</span><span class="s">`meta[name="description"]`</span><span class="p">)</span><span class="o">.</span><span class="n">GetAttribute</span><span class="p">(</span><span class="s">"content"</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Info</span><span class="p">(</span><span class="s">"could not get meta description"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="n">description</span> <span class="o">=</span> <span class="s">""</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">page</span><span class="o">.</span><span class="n">Screenshot</span><span class="p">(</span><span class="n">playwright</span><span class="o">.</span><span class="n">PageScreenshotOptions</span><span class="p">{</span>
<span class="n">Path</span><span class="o">:</span> <span class="n">playwright</span><span class="o">.</span><span class="n">String</span><span class="p">(</span><span class="n">filePath</span><span class="p">),</span>
<span class="p">});</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to create screenshot: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">fp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">os</span><span class="o">.</span><span class="n">Open</span><span class="p">(</span><span class="n">filePath</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to open screenshot: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">fp</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="n">image</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">google</span><span class="o">.</span><span class="n">NewImageFromReader</span><span class="p">(</span><span class="n">fp</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to load screenshot: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">annotations</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">wf</span><span class="o">.</span><span class="n">vision</span><span class="o">.</span><span class="n">DetectSafeSearch</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">image</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to detect labels: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">annotations</span><span class="o">.</span><span class="n">Adult</span> <span class="o">>=</span> <span class="n">visionpb</span><span class="o">.</span><span class="n">Likelihood_POSSIBLE</span> <span class="o">||</span> <span class="n">annotations</span><span class="o">.</span><span class="n">Violence</span> <span class="o">>=</span> <span class="n">visionpb</span><span class="o">.</span><span class="n">Likelihood_POSSIBLE</span> <span class="o">||</span> <span class="n">annotations</span><span class="o">.</span><span class="n">Racy</span> <span class="o">>=</span> <span class="n">visionpb</span><span class="o">.</span><span class="n">Likelihood_POSSIBLE</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"site is not safe %v"</span><span class="p">,</span> <span class="n">annotations</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">cmd</span> <span class="o">:=</span> <span class="n">exec</span><span class="o">.</span><span class="n">Command</span><span class="p">(</span><span class="s">"convert"</span><span class="p">,</span> <span class="n">filePath</span><span class="p">,</span> <span class="s">"-resize"</span><span class="p">,</span> <span class="s">"50%"</span><span class="p">,</span> <span class="s">"-filter"</span><span class="p">,</span> <span class="s">"Lanczos"</span><span class="p">,</span> <span class="s">"-quality"</span><span class="p">,</span> <span class="n">quality</span><span class="p">,</span> <span class="n">filePath</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">cmd</span><span class="o">.</span><span class="n">Run</span><span class="p">();</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"error during image conversion: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">wf</span><span class="o">.</span><span class="n">mc</span><span class="o">.</span><span class="n">FPutObject</span><span class="p">(</span><span class="n">ctx</span><span class="p">,</span> <span class="n">bucket</span><span class="p">,</span> <span class="n">fileName</span><span class="p">,</span> <span class="n">filePath</span><span class="p">,</span> <span class="n">minio</span><span class="o">.</span><span class="n">PutObjectOptions</span><span class="p">{</span><span class="n">ContentType</span><span class="o">:</span> <span class="n">mimetype</span><span class="p">})</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to upload file to minio: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">=</span> <span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="o">.</span><span class="n">Exec</span><span class="p">(</span><span class="s">"UPDATE data SET ready = 1, title = ?, description = ? WHERE url = ?"</span><span class="p">,</span> <span class="n">title</span><span class="p">,</span> <span class="n">description</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"failed to update database record: %v"</span><span class="p">,</span> <span class="n">err</span><span class="p">)</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">message</span><span class="p">)</span>
<span class="n">setError</span><span class="p">(</span><span class="n">wf</span><span class="o">.</span><span class="n">db</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span><span class="p">)</span>
<span class="k">return</span>
<span class="p">}</span>
<span class="k">go</span> <span class="n">warmup</span><span class="p">(</span><span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"%s/%s"</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"DOMAIN"</span><span class="p">),</span> <span class="n">short</span><span class="p">))</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">setError</span><span class="p">(</span><span class="n">db</span> <span class="o">*</span><span class="n">sql</span><span class="o">.</span><span class="n">DB</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">message</span> <span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="n">_</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">db</span><span class="o">.</span><span class="n">Exec</span><span class="p">(</span><span class="s">"UPDATE data SET ready = 1, error = ? WHERE url = ?"</span><span class="p">,</span> <span class="n">message</span><span class="p">,</span> <span class="n">url</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="s">"failed to update database error record"</span><span class="p">,</span> <span class="n">zap</span><span class="o">.</span><span class="n">Error</span><span class="p">(</span><span class="n">err</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s noticeable that it took a bit more effort than if I had used a task queue. However, it turned out to be quite robust and easy to debug, and for that reason alone, it was worth it.</p>
<h3 id="conclusion">Conclusion</h3>
<p>In sharing the shortened link (<a href="https://takealook.pro/4MP">https://takealook.pro/4MP</a>) for my company <a href="https://ultratech.software/">Ultratech Software</a> on social networks, you can have a really nice preview, as seen below.</p>
<p><img src="https://nullonerror.org/public/2024-06-05-how-i-saved-a-few-dozen-dollars-by-migrating-my-personal-projects-from-the-cloud-to-a-raspberry-pi/takealook.pro.avif" alt="takealook.pro" class="center" /></p>
<p><a href="https://takealook.pro/">Take A Look</a></p>
2024-06-05T00:00:00+00:00
Rodrigo Delduca
-
Rodrigo Delduca: My 2024 Setup
https://nullonerror.org/2024/03/20/my-2024-setup/
<h3 id="software--productivity">Software & Productivity</h3>
<ol>
<li><a href="https://brew.sh/">brew</a> as package manager</li>
<li><a href="https://asdf-vm.com/">asdf</a> as language & tools management</li>
<li><a href="https://zed.dev/">Zed</a> as code editor</li>
<li><a href="https://bear.app/">Bear</a> as notes keeping</li>
<li><a href="https://www.mozilla.org/en-US/firefox/new/">Firefox</a> as browser</li>
<li><a href="https://kagi.com/">Kagi</a> as search engine</li>
<li><a href="https://chat.openai.com/">ChatGPT</a> as AI assistant</li>
<li><a href="https://www.usebruno.com/">Bruno</a> as API client</li>
<li><a href="https://orbstack.dev/">OrbStack</a> as Docker desktop replacement, plus virtual machines</li>
<li>Apple ecosystem for mail, calendar, reminders, music, messaging, and more.</li>
</ol>
<h3 id="degoogling">Degoogling</h3>
<p>As can be seen, I am using Kagi, which delivers excellent results without any garbage or ads, instead of Google. I also use email with my own domain through Apple’s iCloud email system, which allows for custom domains, as well as calendar and other features, all outside Google’s domain. Unfortunately, I have to maintain my Google account to use YouTube and occasionally for sites that do not allow creating accounts with a username and password, but only through Single Sign-On (SSO).</p>
2024-03-20T00:00:00+00:00
Rodrigo Delduca
-
Rodrigo Delduca: Mastering Atomic Complex Operations in Redis: Unlocking High-Performance Data Handling With Lua
https://nullonerror.org/2024/03/06/mastering-atomic-complex-operations-in-redis-unlocking-high-performance-data-handling-with-lua/
<p>I have been using Redis for its most basic operations such as caching with expiration and counters, as well as slightly more complex operations like <a href="https://redis.io/docs/data-types/sorted-sets/">zsets</a>, <a href="https://redis.io/commands/zrangebyscore/">zrangebyscore</a>, and <a href="https://redis.io/commands/scan/">scan</a>, or as a <a href="https://redis.io/commands/rpoplpush/">queue</a> for probably more than 6 years on a daily basis.</p>
<p>I won’t discuss Postgres today, as we can achieve similar results. Today, I will introduce something different: the possibility of scripting Redis with <a href="https://en.wikipedia.org/wiki/Lua_%28programming_language%29">Lua</a>.</p>
<p>Months ago, I found myself facing the following problem: I needed to retrieve a JSON string from Redis using a specific key, merge it with the local JSON, and then submit it back to Redis as a string.</p>
<p>This would have been a trivial task if not for the following scenario: On the “other” side, there was a process that consumed the same JSON and then, in an atomic pipeline operation, deleted it.</p>
<p>Given this context, I could not perform the JSON merge on the client side, as it would not be atomic. This is where my native language, Lua (incidentally, I have a dog with the same name, and another dog named Python, yes, I’m a nerd, how did you guess?), comes into play.</p>
<p>With Lua, operations are atomic and accelerated with JIT, making it possible to do a myriad of things, one very classic example being a rate limiter by IP address.</p>
<p>Let’s see how my solution turned out:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">script</span> <span class="o">=</span> <span class="s2">`
local key = KEYS[1]
local input = ARGV[1]
local existing = redis.call('GET', key)
if existing then
local existingJson = cjson.decode(existing)
local inputJson = cjson.decode(input)
for _, v in ipairs(inputJson) do
table.insert(existingJson, v)
end
input = cjson.encode(existingJson)
end
redis.call('SET', key, input)
return input
`</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">key</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">...</span><span class="dl">"</span><span class="p">;</span>
<span class="k">await</span> <span class="nx">redis</span><span class="p">.</span><span class="nb">eval</span><span class="p">(</span><span class="nx">script</span><span class="p">,</span> <span class="p">{</span>
<span class="na">keys</span><span class="p">:</span> <span class="p">[</span><span class="nx">key</span><span class="p">],</span>
<span class="na">arguments</span><span class="p">:</span> <span class="p">[</span><span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">(</span><span class="nx">schema</span><span class="p">.</span><span class="nx">parse</span><span class="p">(</span><span class="nx">data</span><span class="p">))],</span>
<span class="p">});</span>
</code></pre></div></div>
<p>And on the other side, the consumer can retrieve the JSON and delete it, atomically within a pipeline:</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">pipeline</span> <span class="o">=</span> <span class="nx">redis</span><span class="p">.</span><span class="nx">multi</span><span class="p">();</span>
<span class="nx">pipeline</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">...</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">pipeline</span><span class="p">.</span><span class="nx">del</span><span class="p">(</span><span class="dl">"</span><span class="s2">...</span><span class="dl">"</span><span class="p">);</span>
<span class="kd">const</span> <span class="p">[</span><span class="nx">jsonStr</span><span class="p">]</span> <span class="o">=</span> <span class="nx">pipeline</span><span class="p">.</span><span class="nx">exec</span><span class="p">();</span>
</code></pre></div></div>
<p>In this way, all operations are atomic.</p>
<p><img src="https://nullonerror.org/public/2024-03-06-mastering-atomic-complex-operations-in-redis-unlocking-high-performance-data-handling-with-lua/elmo.jpg" alt="Elmo reacting to the atomicity of combining Lua with Redis" class="center" /></p>
2024-03-06T00:00:00+00:00
Rodrigo Delduca
-
Rodrigo Delduca: 2023 was a productive year
https://nullonerror.org/2023/12/18/2023-was-a-productive-year/
<p>The year started and I joined <a href="https://fueled.com/">Fueled</a>, an agency specializing in mobile apps and web systems, as a lead engineer.</p>
<p>At Fueled, so far I’ve had the opportunity to work with three of their biggest clients to date. One of them is the world’s most famous lingerie brand, for which I developed a ‘social network’ that currently has about 5 million users. Then, I worked on a credit platform for elders in the United States that garnered 500,000 users at launch. Currently, I am working on a personal time management project for one of the top coaches in the field.</p>
<p>In parallel, I wrote five articles for Fueled’s technical blog. They are:</p>
<ul>
<li><a href="https://fueled.com/the-cache/posts/backend/working-with-compressed-binary-data-on-aws-iot-core/">Working With Compressed Binary Data on AWS Iot Core</a></li>
<li><a href="https://fueled.com/the-cache/posts/backend/combating-telegram-spam-with-a-serverless-bot/">Combating Telegram Spam With a Serverless Bot</a></li>
<li><a href="https://fueled.com/the-cache/posts/backend/clean-architecture-with-fastapi/">Clean Architecture with FastAPI</a></li>
<li><a href="https://fueled.com/the-cache/posts/backend/devops/cloud-workspace/">Secure Cloud Workspace with VSCode and Tailscale</a></li>
<li><a href="https://fueled.com/the-cache/posts/backend/golang/lint-all-the-things-with-golangci-lint/">Lint all the things with golangci-lint!</a></li>
</ul>
<p>I also wrote another five for my personal blog (this one). They are:</p>
<ul>
<li><a href="https://nullonerror.org/2023/01/19/gotta-go-fast-with-docker-on-macos/">Gotta go fast with Docker on macOS</a></li>
<li><a href="https://nullonerror.org/2023/10/17/working-with-compressed-binary-data-on-aws-iot-core/">Working with compressed binary data on AWS IoT Core</a></li>
<li><a href="https://nullonerror.org/2023/11/01/what-i-ve-been-automating-with-github-actions-an-automated-life/">What I’ve been automating with GitHub Actions, an automated life</a></li>
<li><a href="https://nullonerror.org/2023/11/12/flipping-bits-whilst-updating-pixels/">Flipping Bits Whilst Updating Pixels</a></li>
<li><a href="https://nullonerror.org/2023/12/01/executing-untrusted-code-in-serverless-environments-a-telegram-bot-for-running-c-and-c++-code-on-cloud-run/">Executing Untrusted Code in Serverless Environments: A Telegram Bot for Running C and C++ Code on Cloud Run</a></li>
</ul>
<p>As if that wasn’t enough, I also developed and maintained personal projects, one of which, <a href="https://github.com/flippingpixels/carimbo">Carimbo</a>, was the one I devoted most of my time to. They are:</p>
<ul>
<li><a href="https://github.com/flippingpixels/carimbo">Carimbo</a> - A 2D engine written in C++ using SDL and Lua as a scripting language.</li>
<li><a href="https://github.com/flippingpixels/play">Carimbo Play</a> - A web server to serve a combination of Carimbo releases and games.</li>
<li><a href="https://github.com/skhaz/grammateus">Grammateus</a> - An assistant that summarizes Twitch streams using OpenAI, written in Go.</li>
<li><a href="https://github.com/skhaz/neo-compiler-and-runner">Neo Compiler Bot</a> - A Telegram bot capable of compiling and executing unsafe code in the cloud.</li>
<li><a href="https://github.com/skhaz/freudian-slip">Freudian Slip Bot</a> - A Telegram bot designed to detect slip-ups between the lines.</li>
</ul>
<p>And many other small projects.</p>
<p>As a result.</p>
<p><img src="https://nullonerror.org/public/2023-12-18-2023-was-a-productive-year/github.png" alt="GitHub Stats" /></p>
<p>Let’s see if 2024 will surpass it. Happy new year everyone!</p>
2023-12-18T00:00:00+00:00
Rodrigo Delduca
-
Rodrigo Delduca: Executing Untrusted Code in Serverless Environments: A Telegram Bot for Running C and C++ Code on Cloud Run
https://nullonerror.org/2023/12/01/executing-untrusted-code-in-serverless-environments-a-telegram-bot-for-running-c-and-c++-code-on-cloud-run/
<h2 id="intro">Intro</h2>
<p>I enjoy experimenting and writing Telegram bots for programming groups I participate in. In two groups, people frequently ask about C or C++ code, seeking help, examples, and more. Instead of using online tools like <a href="https://godbolt.org/">Godbolt</a> (Compiler Explorer), they prefer sending their code directly in messages.</p>
<p>I had previously created such a bot using a Flask webserver, which communicated with another container through <a href="https://www.jsonrpc.org/">JSON-RPC</a>. It worked well but occasionally had issues.</p>
<p>With the rise of <a href="https://en.wikipedia.org/wiki/Large_language_model">LLM</a>, I switched to using OpenAI, but many users complained about the unconventional results, which was amusing.</p>
<p>Recently, while working on a project named <a href="https://github.com/flippingpixels/carimbo">Carimbo</a>, I started exploring <a href="https://webassembly.org/">WebAssembly</a>. I realized it could be ideal for running untrusted code. Initially, I considered using <code class="language-plaintext highlighter-rouge">isolated-vm</code> with WebAssembly, but I was quite satisfied with <a href="https://wasmtime.dev/">Wasmtime</a>. It offered options to limit CPU time and RAM usage, among other features.</p>
<h2 id="cgroups">Cgroups</h2>
<p>Any experienced developer would likely suggest using <a href="https://en.wikipedia.org/wiki/Cgroups">cgroups</a> and <a href="https://en.wikipedia.org/wiki/Linux_namespaces">namespaces</a>, which are indeed superior options. However, I prefer not to incur the costs of VMs or keep a machine running 24/7 at my home. This is primarily because <a href="https://cloud.google.com/run">Cloud Run</a>, based on <a href="https://www.docker.com/">Docker</a>, already utilizes cgroups, and to my knowledge, nested cgroups aren’t possible.</p>
<p>Cloud Run offers me several advantages. Without delving into too much detail, it’s a serverless platform built on top of <a href="https://kubernetes.io/">Kubernetes</a>, employing <a href="https://gvisor.dev/">gVisor</a> for an added security layer. You don’t need to handle Kubernetes directly, but the option for fine-tuning is available, which I will discuss in this article.</p>
<h2 id="the-bot">The Bot</h2>
<p>Unlike in my previous work <a href="https://nullonerror.org/2021/01/08/hosting-telegram-bots-on-google-cloud-run/">Hosting Telegram bots on Cloud Run for free</a>, this time I will not use <a href="https://flask.palletsprojects.com">Flask</a>, but instead, I will directly employ <a href="https://www.starlette.io/">Starlette</a>. Starlette is an asynchronous framework for Python. One of the main reasons for this migration is to utilize <a href="https://docs.python.org/3/library/asyncio.html">asyncio</a>, which will enable handling more requests. Additionally, the python-telegram-robot library has shifted to this asynchronous model, aligning with this change.</p>
<p>Let’s start with the Dockerfile.</p>
<div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="w"> </span><span class="s">python:3.12-slim-bookworm</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">base</span>
<span class="k">ENV</span><span class="s"> PIP_DISABLE_PIP_VERSION_CHECK 1</span>
<span class="k">ENV</span><span class="s"> PYTHONUNBUFFERED 1</span>
<span class="k">ENV</span><span class="s"> PYTHONDONTWRITEBYTECODE 1</span>
<span class="k">ENV</span><span class="s"> EMSDK=/emsdk</span>
<span class="k">ENV</span><span class="s"> PATH=/emsdk:/emsdk/upstream/emscripten:/opt/venv/bin:$PATH</span>
<span class="k">FROM</span><span class="w"> </span><span class="s">base</span><span class="w"> </span><span class="k">AS</span><span class="w"> </span><span class="s">builder</span>
<span class="k">RUN </span>python <span class="nt">-m</span> venv /opt/venv
<span class="k">COPY</span><span class="s"> requirements.txt .</span>
<span class="k">RUN </span>pip <span class="nb">install</span> <span class="nt">--no-cache-dir</span> <span class="nt">--requirement</span> requirements.txt
<span class="k">FROM</span><span class="s"> base</span>
<span class="k">WORKDIR</span><span class="s"> /opt/app</span>
<span class="c"># Let's steal this entire directory from the official Emscripten image.</span>
<span class="k">COPY</span><span class="s"> --from=emscripten/emsdk:3.1.49 /emsdk /emsdk</span>
<span class="k">COPY</span><span class="s"> --from=builder /opt/venv /opt/venv</span>
<span class="k">COPY</span><span class="s"> . .</span>
<span class="k">RUN </span>useradd <span class="nt">-r</span> user
<span class="k">USER</span><span class="s"> user</span>
<span class="c"># Instead of Gunicorn, we will use Uvicorn, which is an ASGI web server implementation for Python.</span>
<span class="k">CMD</span><span class="s"> exec uvicorn main:app --host 0.0.0.0 --port $PORT --workers 8 --timeout-keep-alive 600 --timeout-graceful-shutdown 600</span>
</code></pre></div></div>
<p>The main differences are that we steal an entire directory from the Emscripten Docker image, which saves us from having to build in the image, which is excellent. We also use Uvicorn, an ASGI web server that allows direct use of asyncio.</p>
<p>Now let’s see how it goes with handling the incoming requests.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">equals</span><span class="p">(</span><span class="n">left</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="bp">None</span><span class="p">,</span> <span class="n">right</span><span class="p">:</span> <span class="nb">str</span> <span class="o">|</span> <span class="bp">None</span><span class="p">)</span> <span class="o">-></span> <span class="nb">bool</span><span class="p">:</span>
<span class="s">"""
Compare two strings using a consistent amount of time to avoid timing attacks.
"""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">left</span> <span class="ow">or</span> <span class="ow">not</span> <span class="n">right</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">left</span><span class="p">)</span> <span class="o">!=</span> <span class="nb">len</span><span class="p">(</span><span class="n">right</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">for</span> <span class="n">c1</span><span class="p">,</span> <span class="n">c2</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="n">left</span><span class="p">,</span> <span class="n">right</span><span class="p">):</span>
<span class="k">if</span> <span class="n">c1</span> <span class="o">!=</span> <span class="n">c2</span><span class="p">:</span>
<span class="k">return</span> <span class="bp">False</span>
<span class="k">return</span> <span class="bp">True</span>
<span class="k">async</span> <span class="k">def</span> <span class="nf">webhook</span><span class="p">(</span><span class="n">request</span><span class="p">:</span> <span class="n">Request</span><span class="p">):</span>
<span class="s">"""
Entry point for requests coming from Telegram.
"""</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">equals</span><span class="p">(</span>
<span class="n">request</span><span class="p">.</span><span class="n">headers</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"X-Telegram-Bot-Api-Secret-Token"</span><span class="p">),</span>
<span class="n">os</span><span class="p">.</span><span class="n">environ</span><span class="p">[</span><span class="s">"SECRET"</span><span class="p">],</span>
<span class="p">):</span>
<span class="c1"># This section prevents false calls, only this application and Telegram know the secret.
</span> <span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">status_code</span><span class="o">=</span><span class="mi">401</span><span class="p">)</span>
<span class="n">payload</span> <span class="o">=</span> <span class="k">await</span> <span class="n">request</span><span class="p">.</span><span class="n">json</span><span class="p">()</span>
<span class="c1"># Where the bot becomes operational, the JSON is passed to the application, which in turn processes the request.
</span> <span class="k">async</span> <span class="k">with</span> <span class="n">application</span><span class="p">:</span>
<span class="k">await</span> <span class="n">application</span><span class="p">.</span><span class="n">process_update</span><span class="p">(</span><span class="n">Update</span><span class="p">.</span><span class="n">de_json</span><span class="p">(</span><span class="n">payload</span><span class="p">,</span> <span class="n">application</span><span class="p">.</span><span class="n">bot</span><span class="p">))</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">status_code</span><span class="o">=</span><span class="mi">200</span><span class="p">)</span>
<span class="n">app</span> <span class="o">=</span> <span class="n">Starlette</span><span class="p">(</span>
<span class="n">routes</span><span class="o">=</span><span class="p">[</span>
<span class="n">Route</span><span class="p">(</span><span class="s">"/"</span><span class="p">,</span> <span class="n">webhook</span><span class="p">,</span> <span class="n">methods</span><span class="o">=</span><span class="p">[</span><span class="s">"POST"</span><span class="p">]),</span>
<span class="p">],</span>
<span class="p">)</span>
</code></pre></div></div>
<p>Finally, we have the handler for messages that start with <code class="language-plaintext highlighter-rouge">/run</code>.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">async</span> <span class="k">def</span> <span class="nf">on_run</span><span class="p">(</span><span class="n">update</span><span class="p">:</span> <span class="n">Update</span><span class="p">,</span> <span class="n">context</span><span class="p">:</span> <span class="n">ContextTypes</span><span class="p">.</span><span class="n">DEFAULT_TYPE</span><span class="p">)</span> <span class="o">-></span> <span class="bp">None</span><span class="p">:</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">update</span><span class="p">.</span><span class="n">message</span><span class="p">.</span><span class="n">reply_to_message</span> <span class="ow">or</span> <span class="n">update</span><span class="p">.</span><span class="n">message</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">message</span><span class="p">:</span>
<span class="k">return</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">message</span><span class="p">.</span><span class="n">text</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">text</span><span class="p">:</span>
<span class="k">return</span>
<span class="n">text</span> <span class="o">=</span> <span class="n">text</span><span class="p">.</span><span class="n">lstrip</span><span class="p">(</span><span class="s">"/run"</span><span class="p">)</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">text</span><span class="p">:</span>
<span class="k">await</span> <span class="n">message</span><span class="p">.</span><span class="n">reply_text</span><span class="p">(</span><span class="s">"Luke, I need the code for the Death Star's system."</span><span class="p">)</span>
<span class="k">return</span>
<span class="k">try</span><span class="p">:</span>
<span class="c1"># All the code is asynchronous, while the 'run' function is not. Therefore, we execute it in a thread.
</span> <span class="n">coro</span> <span class="o">=</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">to_thread</span><span class="p">(</span><span class="n">run</span><span class="p">,</span> <span class="n">text</span><span class="p">)</span>
<span class="c1"># We execute the thread as a coroutine and limit its execution to 30 seconds.
</span> <span class="n">result</span> <span class="o">=</span> <span class="k">await</span> <span class="n">asyncio</span><span class="p">.</span><span class="n">wait_for</span><span class="p">(</span><span class="n">coro</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">30</span><span class="p">)</span>
<span class="c1"># Below, we prevent flooding in groups by placing very long messages into a bucket and returning the public URL.
</span> <span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">result</span><span class="p">)</span> <span class="o">></span> <span class="mi">64</span><span class="p">:</span>
<span class="n">blob</span> <span class="o">=</span> <span class="n">bucket</span><span class="p">.</span><span class="n">blob</span><span class="p">(</span><span class="n">hashlib</span><span class="p">.</span><span class="n">sha256</span><span class="p">(</span><span class="nb">str</span><span class="p">(</span><span class="n">text</span><span class="p">).</span><span class="n">encode</span><span class="p">()).</span><span class="n">hexdigest</span><span class="p">())</span>
<span class="n">blob</span><span class="p">.</span><span class="n">upload_from_string</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="n">blob</span><span class="p">.</span><span class="n">make_public</span><span class="p">()</span>
<span class="n">result</span> <span class="o">=</span> <span class="n">blob</span><span class="p">.</span><span class="n">public_url</span>
<span class="c1"># Respond to the message with the result, which can be either an error or a success.
</span> <span class="k">await</span> <span class="n">message</span><span class="p">.</span><span class="n">reply_text</span><span class="p">(</span><span class="n">result</span><span class="p">)</span>
<span class="k">except</span> <span class="n">asyncio</span><span class="p">.</span><span class="nb">TimeoutError</span><span class="p">:</span>
<span class="c1"># If the code exceeds the time limit or takes too long to compile, we return some emojis.
</span> <span class="k">await</span> <span class="n">message</span><span class="p">.</span><span class="n">reply_text</span><span class="p">(</span><span class="s">"⏰😮💨"</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="running-untrusted-code">Running Untrusted Code</h2>
<p>Each request to execute code is compiled using em++, an ‘alias’ for clang++, targeting WebAssembly, and then executed with the <a href="https://wasi.dev/">WASI</a> runtime. Each execution runs separately and in a thread-safe manner in its own directory. While I could limit CPU usage (fuel) and memory usage, as indicated by the commented lines, in my case I opted for a container with 4GB of RAM and 4 vCPUs, which is more than sufficient given that I configured Run to accept only 8 connections per instance.</p>
<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="n">source</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>
<span class="k">with</span> <span class="n">TemporaryDirectory</span><span class="p">()</span> <span class="k">as</span> <span class="n">path</span><span class="p">:</span>
<span class="n">os</span><span class="p">.</span><span class="n">chdir</span><span class="p">(</span><span class="n">path</span><span class="p">)</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"main.cpp"</span><span class="p">,</span> <span class="s">"w+t"</span><span class="p">)</span> <span class="k">as</span> <span class="n">main</span><span class="p">:</span>
<span class="n">main</span><span class="p">.</span><span class="n">write</span><span class="p">(</span><span class="n">source</span><span class="p">)</span>
<span class="n">main</span><span class="p">.</span><span class="n">flush</span><span class="p">()</span>
<span class="k">try</span><span class="p">:</span>
<span class="c1"># Compile it.
</span> <span class="n">result</span> <span class="o">=</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">run</span><span class="p">(</span>
<span class="p">[</span>
<span class="s">"em++"</span><span class="p">,</span>
<span class="s">"-s"</span><span class="p">,</span>
<span class="s">"ENVIRONMENT=node"</span><span class="p">,</span>
<span class="s">"-s"</span><span class="p">,</span>
<span class="s">"WASM=1"</span><span class="p">,</span>
<span class="s">"-s"</span><span class="p">,</span>
<span class="s">"PURE_WASI=1"</span><span class="p">,</span>
<span class="s">"main.cpp"</span><span class="p">,</span>
<span class="p">],</span>
<span class="n">capture_output</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="n">text</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="n">check</span><span class="o">=</span><span class="bp">True</span><span class="p">,</span>
<span class="p">)</span>
<span class="k">if</span> <span class="n">result</span><span class="p">.</span><span class="n">returncode</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">return</span> <span class="n">result</span><span class="p">.</span><span class="n">stderr</span>
<span class="c1"># Run it.
</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"a.out.wasm"</span><span class="p">,</span> <span class="s">"rb"</span><span class="p">)</span> <span class="k">as</span> <span class="n">binary</span><span class="p">:</span>
<span class="n">wasi</span> <span class="o">=</span> <span class="n">WasiConfig</span><span class="p">()</span>
<span class="c1"># Store the output in a file.
</span> <span class="n">wasi</span><span class="p">.</span><span class="n">stdout_file</span> <span class="o">=</span> <span class="s">"a.out.stdout"</span>
<span class="c1"># Store the errors in a file.
</span> <span class="n">wasi</span><span class="p">.</span><span class="n">stderr_file</span> <span class="o">=</span> <span class="s">"a.out.stderr"</span>
<span class="n">config</span> <span class="o">=</span> <span class="n">Config</span><span class="p">()</span>
<span class="c1"># config.consume_fuel = True
</span> <span class="n">engine</span> <span class="o">=</span> <span class="n">Engine</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
<span class="n">store</span> <span class="o">=</span> <span class="n">Store</span><span class="p">(</span><span class="n">engine</span><span class="p">)</span>
<span class="n">store</span><span class="p">.</span><span class="n">set_wasi</span><span class="p">(</span><span class="n">wasi</span><span class="p">)</span>
<span class="c1"># Limits the RAM.
</span> <span class="c1"># store.set_limits(16 * 1024 * 1024)
</span> <span class="c1"># Limits the CPU.
</span> <span class="c1"># store.set_fuel(10_000_000_000)
</span>
<span class="n">linker</span> <span class="o">=</span> <span class="n">Linker</span><span class="p">(</span><span class="n">engine</span><span class="p">)</span>
<span class="n">linker</span><span class="p">.</span><span class="n">define_wasi</span><span class="p">()</span>
<span class="n">module</span> <span class="o">=</span> <span class="n">Module</span><span class="p">(</span><span class="n">store</span><span class="p">.</span><span class="n">engine</span><span class="p">,</span> <span class="n">binary</span><span class="p">.</span><span class="n">read</span><span class="p">())</span>
<span class="n">instance</span> <span class="o">=</span> <span class="n">linker</span><span class="p">.</span><span class="n">instantiate</span><span class="p">(</span><span class="n">store</span><span class="p">,</span> <span class="n">module</span><span class="p">)</span>
<span class="c1"># `_start` is the binary entrypoint, also known as main.
</span> <span class="n">start</span> <span class="o">=</span> <span class="n">instance</span><span class="p">.</span><span class="n">exports</span><span class="p">(</span><span class="n">store</span><span class="p">)[</span><span class="s">"_start"</span><span class="p">]</span>
<span class="k">assert</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">start</span><span class="p">,</span> <span class="n">Func</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">start</span><span class="p">(</span><span class="n">store</span><span class="p">)</span>
<span class="k">except</span> <span class="n">ExitTrap</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="c1"># If exit code is not 0, we return the errors.
</span> <span class="k">if</span> <span class="n">e</span><span class="p">.</span><span class="n">code</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"a.out.stderr"</span><span class="p">,</span> <span class="s">"rt"</span><span class="p">)</span> <span class="k">as</span> <span class="n">stderr</span><span class="p">:</span>
<span class="k">return</span> <span class="n">stderr</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
<span class="c1"># If no errors, we return the output.
</span> <span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s">"a.out.stdout"</span><span class="p">,</span> <span class="s">"rt"</span><span class="p">)</span> <span class="k">as</span> <span class="n">stdout</span><span class="p">:</span>
<span class="k">return</span> <span class="n">stdout</span><span class="p">.</span><span class="n">read</span><span class="p">()</span>
<span class="k">except</span> <span class="n">subprocess</span><span class="p">.</span><span class="n">CalledProcessError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
<span class="k">return</span> <span class="n">e</span><span class="p">.</span><span class="n">stderr</span>
<span class="k">except</span> <span class="nb">Exception</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span> <span class="c1"># noqa
</span> <span class="k">return</span> <span class="nb">str</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
</code></pre></div></div>
<h2 id="deploy">Deploy</h2>
<p>In the past, I always used Google’s tools for deployment, but this time I tried building the Docker image in GitHub Action, which gave me two huge advantages.</p>
<ol>
<li>Cache: I don’t know why, but I never got the cache to work in Cloud Build. With GitHub, it’s just a matter of using a flag.</li>
<li>Modern Docker syntax usage: In Cloud Build, it’s not possible to use <a href="https://www.docker.com/blog/introduction-to-heredocs-in-dockerfiles/">heredoc</a>, for example.</li>
<li>Speed: I know it’s possible to upgrade the Cloud Build machine, but that costs money, and on GitHub, I have a quite generous free quota.</li>
</ol>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Deploy on Google Cloud Platform</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">push</span><span class="pi">:</span>
<span class="na">branches</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">main</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">deploy</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Authenticate to Google Cloud</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">google-github-actions/auth@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">credentials_json</span><span class="pi">:</span> <span class="s">$</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set up Google Cloud SDK</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">google-github-actions/setup-gcloud@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">project_id</span><span class="pi">:</span> <span class="s">$</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set up Docker Buildx</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">docker/setup-buildx-action@v3</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Authenticate Docker</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">gcloud auth configure-docker --quiet $-docker.pkg.dev</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build And Push Telegram Service</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">docker/build-push-action@v5</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">context</span><span class="pi">:</span> <span class="s">.</span>
<span class="na">push</span><span class="pi">:</span> <span class="no">true</span>
<span class="na">tags</span><span class="pi">:</span> <span class="s">$/$:$</span>
<span class="na">cache-from</span><span class="pi">:</span> <span class="s">type=gha</span>
<span class="na">cache-to</span><span class="pi">:</span> <span class="s">type=gha,mode=max</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Deploy Telegram Service to Cloud Run</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">TELEGRAM_SERVICE_NAME</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">REGION</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">REGISTRY</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">GITHUB_SHA</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">TELEGRAM_TOKEN</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">SECRET</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">BUCKET</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">cat <<EOF | envsubst > service.yaml</span>
<span class="s">apiVersion: serving.knative.dev/v1</span>
<span class="s">kind: Service</span>
<span class="s">metadata:</span>
<span class="s">name: "$TELEGRAM_SERVICE_NAME"</span>
<span class="s">labels:</span>
<span class="s">cloud.googleapis.com/location: "$REGION"</span>
<span class="s">spec:</span>
<span class="s">template:</span>
<span class="s">metadata:</span>
<span class="s">annotations:</span>
<span class="s">run.googleapis.com/execution-environment: "gen2"</span>
<span class="s">run.googleapis.com/startup-cpu-boost: "true"</span>
<span class="s">run.googleapis.com/cpu-throttling: "true"</span>
<span class="s">autoscaling.knative.dev/maxScale: "16"</span>
<span class="s">spec:</span>
<span class="s">containerConcurrency: "1"</span>
<span class="s">timeoutSeconds: "60"</span>
<span class="s">containers:</span>
<span class="s">- image: "$REGISTRY/$TELEGRAM_SERVICE_NAME:$GITHUB_SHA"</span>
<span class="s">name: "$TELEGRAM_SERVICE_NAME"</span>
<span class="s">resources:</span>
<span class="s">limits:</span>
<span class="s">cpu: "4000m"</span>
<span class="s">memory: "4Gi"</span>
<span class="s">env:</span>
<span class="s">- name: TELEGRAM_TOKEN</span>
<span class="s">value: "$TELEGRAM_TOKEN"</span>
<span class="s">- name: SECRET</span>
<span class="s">value: "$SECRET"</span>
<span class="s">- name: BUCKET</span>
<span class="s">value: "$BUCKET"</span>
<span class="s">EOF</span>
<span class="s">gcloud run services replace service.yaml</span>
<span class="s">rm -f service.yaml</span>
</code></pre></div></div>
<h2 id="conclusion">Conclusion</h2>
<p>Try here: <a href="https://t.me/neo_compiler_bot">https://t.me/neo_compiler_bot</a> or <code class="language-plaintext highlighter-rouge">@neo_compiler_bot</code> on Telegram.</p>
<p>Source code: <a href="https://github.com/skhaz/neo-compiler-and-runner">https://github.com/skhaz/neo-compiler-and-runner</a>.</p>
<p><img src="https://nullonerror.org/public/2023-12-01-executing-untrusted-code-in-serverless-environments-a-telegram-bot-for-running-c-and-c++-code-on-cloud-run/telegram.png" alt="Telegram" class="center" /></p>
2023-12-01T00:00:00+00:00
Rodrigo Delduca
-
Rodrigo Delduca: Flipping Bits Whilst Updating Pixels
https://nullonerror.org/2023/11/12/flipping-bits-whilst-updating-pixels/
<blockquote>
<p>I have less than 16 milliseconds to do more than thousands of operations.</p>
</blockquote>
<p>Roughly 15 years ago, during an extended summer holiday, in a shared room of a student residence, I found myself endeavoring to port <a href="https://github.com/skhaz/wintermoon">my 2D game</a> engine built on top of <a href="https://en.wikipedia.org/wiki/Simple_DirectMedia_Layer">SDL</a> to the <a href="https://en.wikipedia.org/wiki/Google_Native_Client">Google Native Client (NaCl)</a>. NaCl served as a sandboxing mechanism for Chrome, enabling the execution of native code within the browser, specifically within the Chrome browser. It’s safe to assert that NaCl can be considered the progenitor of <a href="https://webassembly.org">WebAssembly</a>.</p>
<p>A considerable amount of time has elapsed, and many changes have transpired. I transitioned from game development to web development, yet low-level programming has always coursed through my veins. Consequently, I resolved to revive the dream of crafting my own game engine running on the web. Today, with the advent of WebAssembly, achieving this goal is significantly more feasible and portable.</p>
<p>Therefore, I created Carimbo 🇧🇷, meaning “stamp” in English. The name encapsulates the notion that 2D engines are continually stamping sprites onto the screen.</p>
<img src="https://nullonerror.org/public/2023-11-12-flipping-bits-whilst-updating-pixels/carimbo.webp" alt="Carimbo" class="center" />
Artwork by <a href="https://www.fiverr.com/yuugenpixie">@yuugenpixie</a>
<p>This engine shares the foundational principles of <em>Wintermoon</em>; it is built upon the SDL library, employs Lua for scripting, and consolidates all assets within a compressed LZMA file, which is <em>mounted</em> when the engine initializes.</p>
<p>In essence, it operates as follows: video, audio, joystick, network, and file system components are initialized. Then, the <code class="language-plaintext highlighter-rouge">bundle.zip</code> file is mounted. When I say ‘mounted,’ I employ <a href="https://icculus.org/physfs/">a library</a> that makes read and write operations within the compressed file entirely transparent, eliminating the need for decompression, which is excellent. Subsequently, the ‘main.lua’ file is executed. This file should utilize the factory to construct the engine, which is the cornerstone. Following this, the script must spawn entities and other objects to be used within the game. Finally, with the game defined, the script should invoke the ‘run’ method of the engine, which will block and initiate the event loop.</p>
<p>However, this time around, the tooling for C++ has significantly improved compared to that era. Numerous package managers now exist, and compilers, along with the standard library, have matured considerably. Notably, there’s no longer a necessity to employ Boost for advanced features; many functionalities, including smart pointers and others formerly associated with Boost, are now integral parts of the standard library.</p>
<p>Speaking of package managers, in Carimbo, I opted for <a href="https://conan.io/">Conan</a>, which, in my opinion, is an excellent package manager.</p>
<p>It was during this exploration that I discovered Conan’s support for various toolings, including <a href="https://github.com/emscripten-core/emsdk">emsdk</a>—the Software Development Kit (SDK) and compiler for the Emscripten project. Emscripten is an LLVM/Clang-based compiler designed to translate C and C++ source code into WebAssembly.</p>
<p>With the <code class="language-plaintext highlighter-rouge">emsdk</code>, I could finally fulfill my long-held aspiration.</p>
<p><img src="https://nullonerror.org/public/2023-11-12-flipping-bits-whilst-updating-pixels/blank.jpeg" alt="" class="center" /></p>
<p>Yes, a black screen and an overwhelming sense of victory. I had successfully ported my code to run in the browser. Now, all that remained was to figure out how to load the assets (at that time). However, the event loop was running just a tad below 60 frames per second due to a bug in the counter.</p>
<p>And you might be wondering, ‘How do you debug this?’ Firstly, for various reasons that can burden the final binary, a lot is stripped away. Therefore, we need to recompile everything and all dependencies with sanitizers and debugging symbols. Secondly, WebAssembly should be linked with <strong>-sASSERTIONS=2</strong>, <strong>-sRUNTIME_DEBUG</strong>, and <strong>–profiling</strong>. This way, it’s possible to see the stack trace in the browser console as if by magic. Additionally, <a href="https://developer.chrome.com/blog/wasm-debugging-2020/">Chrome has a debugger</a> that allows you to insert breakpoints within your source code and inspect step by step.</p>
<p>By the way, a binary and all its dependencies compiled with all sanitizers and debugging symbols can easily surpass 300 megabytes! So, I recommend compiling with -Os or -O1.</p>
<p><a href="https://github.com/carimbolabs/carimbo">The source code for the Carimbo</a></p>
<p>Check out the engine running below; this is just a concept without audio and joystick support.</p>
2023-11-12T00:00:00+00:00
Rodrigo Delduca
-
Rodrigo Delduca: What I’ve been automating with GitHub Actions, an automated life
https://nullonerror.org/2023/11/01/what-i-ve-been-automating-with-github-actions-an-automated-life/
<h1 id="automate-all-the-things">Automate all the things!</h1>
<p>Programmers, like no other beings on the planet, are completely obsessed with automating things, from the simplest to the most complex, and I’m no different.</p>
<p>I have automated several things using GitHub Actions, and today I will show some of the things I’ve done.</p>
<h2 id="readmemd">README.md</h2>
<p>In my GitHub README, I periodically fetch the RSS feed from my blog (the one you are currently reading) and populate it with the latest articles, like this:</p>
<p>Ah, you can use any source of RSS, like your YouTube channel!</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Latest blog post workflow</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">schedule</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0</span><span class="nv"> </span><span class="s">*/6</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*"</span>
<span class="na">workflow_dispatch</span><span class="pi">:</span> <span class="c1"># Run workflow manually</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">update-readme-with-blog</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Update this repo's README with latest blog posts</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v2</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Pull NULL on Error posts</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">gautamkrishnar/blog-post-workflow@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">comment_tag_name</span><span class="pi">:</span> <span class="s">BLOG</span>
<span class="na">commit_message</span><span class="pi">:</span> <span class="s">Update with the latest blog posts</span>
<span class="na">committer_username</span><span class="pi">:</span> <span class="s">Rodrigo Delduca</span>
<span class="na">committer_email</span><span class="pi">:</span> <span class="s">46259+skhaz@users.noreply.github.com</span>
<span class="na">max_post_count</span><span class="pi">:</span> <span class="m">6</span>
<span class="na">feed_list</span><span class="pi">:</span> <span class="s2">"</span><span class="s">https://nullonerror.org/feed"</span>
</code></pre></div></div>
<h2 id="resume">Resume</h2>
<p>My resume is public. I have an action in the repository that compiles and uploads it to a Google Cloud bucket and sets the object as public, like this:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">on</span><span class="pi">:</span> <span class="s">push</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">build</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">container</span><span class="pi">:</span> <span class="s">texlive/texlive</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Build</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">xelatex resume.tex</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Authenticate to Google Cloud</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">google-github-actions/auth@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">credentials_json</span><span class="pi">:</span> <span class="s">$</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Upload to Google Cloud Storage</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">google-github-actions/upload-cloud-storage@v1</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">path</span><span class="pi">:</span> <span class="s">resume.pdf</span>
<span class="na">destination</span><span class="pi">:</span> <span class="s">gcs.skhaz.dev</span>
<span class="na">predefinedAcl</span><span class="pi">:</span> <span class="s">publicRead</span>
</code></pre></div></div>
<p>You can check it out at <a href="https://gcs.skhaz.dev/resume.pdf">https://gcs.skhaz.dev/resume.pdf</a>.</p>
<h2 id="github-stars">GitHub Stars</h2>
<p>I believe everyone enjoys starring repositories, but GitHub’s interface doesn’t help much when it comes to finding or organizing them.</p>
<p>To do this, I use <a href="https://github.com/maguowei/starred">starred</a>, which generates a README file categorizing by language. You can check it out at the following address: <a href="https://github.com/skhaz/stars">https://github.com/skhaz/stars</a></p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Update Stars</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">workflow_dispatch</span><span class="pi">:</span>
<span class="na">schedule</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s">0 0 * * *</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">stars</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">Update stars</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Set up Python</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-python@v3</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">python-version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3.10"</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Install dependencies</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">python -m pip install --upgrade pip</span>
<span class="s">pip install starred</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Get repository name</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">echo "REPOSITORY_NAME=${GITHUB_REPOSITORY#*/}" >> $GITHUB_ENV</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Update repository category by language</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">GITHUB_TOKEN</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">REPOSITORY</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">USERNAME</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">run</span><span class="pi">:</span> <span class="s">starred --username ${USERNAME} --repository ${REPOSITORY} --sort --token ${GITHUB_TOKEN} --message 'awesome-stars category by language update by github actions cron, created by starred'</span>
</code></pre></div></div>
<h2 id="healthchecks">Healthchecks</h2>
<p>I imagine that, just like me, you also have several websites. In my case, all of them are simple and do not generate any income. However, I want to make sure that everything is in order, so I use https://healthchecks.io in conjunction with GitHub Actions. Healthchecks.io operates passively; it does not make requests to your site. On the contrary, you must make a request to it, and then it marks it as healthy. If there are no pings for a certain amount of time (configurable), it will notify you through various means that the site or application is not functioning as it should. Think of it as a Kubernetes probe.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Health Check</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">workflow_dispatch</span><span class="pi">:</span>
<span class="na">schedule</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*"</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">health</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Check health</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">STATUSCODE=$(curl -s -o /dev/null --write-out "%{http_code}" "${SITE_URL}")</span>
<span class="s">if test $STATUSCODE -ne 200; then</span>
<span class="s">exit 1</span>
<span class="s">fi</span>
<span class="s">curl -fsS -m 10 --retry 5 -o /dev/null "https://hc-ping.com/${HEALTH_UUID}"</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">HEALTH_UUID</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">SITE_URL</span><span class="pi">:</span> <span class="s">$</span>
</code></pre></div></div>
<h2 id="salary">Salary</h2>
<p>This one is a bit more complex, as it goes beyond just an action. I have a repository called ‘salary’, where there’s an action that runs every hour. What this action does is essentially run a Go code, and the result updates the README file. This way, I can simply access the URL and get an estimate of how much I’ll receive.</p>
<p>In the program, I have two goroutines running in parallel. In one of them, I query the number of hours on Toggl, multiply it by my rate, and return it through a channel. The other does the same, but it uses an API to convert dollars to Brazilian reais, and in the end, the values are summed up.</p>
<p><code class="language-plaintext highlighter-rouge">main.go</code></p>
<div class="language-go highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="n">main</span>
<span class="k">import</span> <span class="p">(</span>
<span class="s">"bytes"</span>
<span class="s">"encoding/json"</span>
<span class="s">"fmt"</span>
<span class="s">"io"</span>
<span class="s">"log"</span>
<span class="s">"math"</span>
<span class="s">"net/http"</span>
<span class="s">"os"</span>
<span class="s">"strconv"</span>
<span class="s">"sync"</span>
<span class="s">"time"</span>
<span class="p">)</span>
<span class="k">type</span> <span class="n">DateRange</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">StartDate</span> <span class="kt">string</span> <span class="s">`json:"start_date"`</span>
<span class="n">EndDate</span> <span class="kt">string</span> <span class="s">`json:"end_date"`</span>
<span class="p">}</span>
<span class="k">type</span> <span class="n">Summary</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">TrackedSecond</span> <span class="kt">int</span> <span class="s">`json:"tracked_seconds"`</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">toggl</span><span class="p">(</span><span class="n">result</span> <span class="k">chan</span><span class="o"><-</span> <span class="kt">int</span><span class="p">,</span> <span class="n">wg</span> <span class="o">*</span><span class="n">sync</span><span class="o">.</span><span class="n">WaitGroup</span><span class="p">)</span> <span class="p">{</span>
<span class="k">defer</span> <span class="n">wg</span><span class="o">.</span><span class="n">Done</span><span class="p">()</span>
<span class="k">var</span> <span class="p">(</span>
<span class="n">now</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">Now</span><span class="p">()</span>
<span class="n">firstDay</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">Date</span><span class="p">(</span><span class="n">now</span><span class="o">.</span><span class="n">Year</span><span class="p">(),</span> <span class="n">now</span><span class="o">.</span><span class="n">Month</span><span class="p">(),</span> <span class="m">1</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="n">time</span><span class="o">.</span><span class="n">UTC</span><span class="p">)</span>
<span class="n">lastDay</span> <span class="o">=</span> <span class="n">firstDay</span><span class="o">.</span><span class="n">AddDate</span><span class="p">(</span><span class="m">0</span><span class="p">,</span> <span class="m">1</span><span class="p">,</span> <span class="o">-</span><span class="m">1</span><span class="p">)</span>
<span class="n">url</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"https://api.track.toggl.com/reports/api/v3/workspace/%s/projects/summary"</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"TOGGL_WORKSPACE_ID"</span><span class="p">))</span>
<span class="n">dataRange</span> <span class="o">=</span> <span class="n">DateRange</span><span class="p">{</span>
<span class="n">StartDate</span><span class="o">:</span> <span class="n">firstDay</span><span class="o">.</span><span class="n">Format</span><span class="p">(</span><span class="s">"2006-01-02"</span><span class="p">),</span>
<span class="n">EndDate</span><span class="o">:</span> <span class="n">lastDay</span><span class="o">.</span><span class="n">Format</span><span class="p">(</span><span class="s">"2006-01-02"</span><span class="p">),</span>
<span class="p">}</span>
<span class="p">)</span>
<span class="n">payload</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">json</span><span class="o">.</span><span class="n">Marshal</span><span class="p">(</span><span class="n">dataRange</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">req</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">MethodPost</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="n">bytes</span><span class="o">.</span><span class="n">NewBuffer</span><span class="p">(</span><span class="n">payload</span><span class="p">))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"Content-Type"</span><span class="p">,</span> <span class="s">"application/json"</span><span class="p">)</span>
<span class="n">req</span><span class="o">.</span><span class="n">SetBasicAuth</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"TOGGL_EMAIL"</span><span class="p">),</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"TOGGL_PASSWORD"</span><span class="p">))</span>
<span class="n">client</span> <span class="o">:=</span> <span class="o">&</span><span class="n">http</span><span class="o">.</span><span class="n">Client</span><span class="p">{}</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">client</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="n">body</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">ReadAll</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="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">var</span> <span class="n">summaries</span> <span class="p">[]</span><span class="n">Summary</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="o">&</span><span class="n">summaries</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">total</span> <span class="o">:=</span> <span class="m">0</span>
<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">summary</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">summaries</span> <span class="p">{</span>
<span class="n">total</span> <span class="o">+=</span> <span class="n">summary</span><span class="o">.</span><span class="n">TrackedSecond</span>
<span class="p">}</span>
<span class="n">hourlyRate</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">strconv</span><span class="o">.</span><span class="n">Atoi</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"TOGGL_HOURLY_RATE"</span><span class="p">))</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">result</span> <span class="o"><-</span> <span class="p">(</span><span class="n">total</span> <span class="o">/</span> <span class="m">3600</span><span class="p">)</span> <span class="o">*</span> <span class="n">hourlyRate</span>
<span class="p">}</span>
<span class="k">type</span> <span class="n">CurrencyData</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">Quotes</span> <span class="k">struct</span> <span class="p">{</span>
<span class="n">USDBRL</span> <span class="kt">float64</span> <span class="s">`json:"USDBRL"`</span>
<span class="p">}</span> <span class="s">`json:"quotes"`</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">husky</span><span class="p">(</span><span class="n">result</span> <span class="k">chan</span><span class="o"><-</span> <span class="kt">int</span><span class="p">,</span> <span class="n">wg</span> <span class="o">*</span><span class="n">sync</span><span class="o">.</span><span class="n">WaitGroup</span><span class="p">)</span> <span class="p">{</span>
<span class="k">defer</span> <span class="n">wg</span><span class="o">.</span><span class="n">Done</span><span class="p">()</span>
<span class="k">var</span> <span class="p">(</span>
<span class="n">currency</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"HUSKY_CURRENCY"</span><span class="p">)</span>
<span class="n">url</span> <span class="o">=</span> <span class="n">fmt</span><span class="o">.</span><span class="n">Sprintf</span><span class="p">(</span><span class="s">"https://api.apilayer.com/currency_data/live?base=USD&symbols=%s&currencies=%s"</span><span class="p">,</span> <span class="n">currency</span><span class="p">,</span> <span class="n">currency</span><span class="p">)</span>
<span class="p">)</span>
<span class="n">req</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">http</span><span class="o">.</span><span class="n">NewRequest</span><span class="p">(</span><span class="n">http</span><span class="o">.</span><span class="n">MethodGet</span><span class="p">,</span> <span class="n">url</span><span class="p">,</span> <span class="no">nil</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">req</span><span class="o">.</span><span class="n">Header</span><span class="o">.</span><span class="n">Set</span><span class="p">(</span><span class="s">"apikey"</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"APILAYER_APIKEY"</span><span class="p">))</span>
<span class="n">client</span> <span class="o">:=</span> <span class="o">&</span><span class="n">http</span><span class="o">.</span><span class="n">Client</span><span class="p">{}</span>
<span class="n">resp</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">client</span><span class="o">.</span><span class="n">Do</span><span class="p">(</span><span class="n">req</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">defer</span> <span class="n">resp</span><span class="o">.</span><span class="n">Body</span><span class="o">.</span><span class="n">Close</span><span class="p">()</span>
<span class="n">body</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">io</span><span class="o">.</span><span class="n">ReadAll</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="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">var</span> <span class="n">data</span> <span class="n">CurrencyData</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">=</span> <span class="n">json</span><span class="o">.</span><span class="n">Unmarshal</span><span class="p">(</span><span class="n">body</span><span class="p">,</span> <span class="o">&</span><span class="n">data</span><span class="p">);</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">monthlySalary</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">strconv</span><span class="o">.</span><span class="n">ParseFloat</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">Getenv</span><span class="p">(</span><span class="s">"HUSKY_MONTHLY_SALARY"</span><span class="p">),</span> <span class="m">64</span><span class="p">)</span>
<span class="k">if</span> <span class="n">err</span> <span class="o">!=</span> <span class="no">nil</span> <span class="p">{</span>
<span class="n">log</span><span class="o">.</span><span class="n">Fatalln</span><span class="p">(</span><span class="n">err</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">gross</span> <span class="o">:=</span> <span class="kt">int</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">Floor</span><span class="p">(</span><span class="n">monthlySalary</span> <span class="o">*</span> <span class="n">data</span><span class="o">.</span><span class="n">Quotes</span><span class="o">.</span><span class="n">USDBRL</span><span class="p">))</span>
<span class="n">deduction</span> <span class="o">:=</span> <span class="n">gross</span> <span class="o">*</span> <span class="m">1</span> <span class="o">/</span> <span class="m">100</span>
<span class="n">result</span> <span class="o"><-</span> <span class="n">gross</span> <span class="o">-</span> <span class="n">deduction</span>
<span class="p">}</span>
<span class="k">func</span> <span class="n">main</span><span class="p">()</span> <span class="p">{</span>
<span class="k">var</span> <span class="p">(</span>
<span class="n">ch</span> <span class="o">=</span> <span class="nb">make</span><span class="p">(</span><span class="k">chan</span> <span class="kt">int</span><span class="p">)</span>
<span class="n">wg</span> <span class="n">sync</span><span class="o">.</span><span class="n">WaitGroup</span>
<span class="n">funcs</span> <span class="o">=</span> <span class="p">[]</span><span class="k">func</span><span class="p">(</span><span class="k">chan</span><span class="o"><-</span> <span class="kt">int</span><span class="p">,</span> <span class="o">*</span><span class="n">sync</span><span class="o">.</span><span class="n">WaitGroup</span><span class="p">){</span><span class="n">toggl</span><span class="p">,</span> <span class="n">husky</span><span class="p">}</span>
<span class="p">)</span>
<span class="n">wg</span><span class="o">.</span><span class="n">Add</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">funcs</span><span class="p">))</span>
<span class="k">for</span> <span class="n">_</span><span class="p">,</span> <span class="n">fun</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">funcs</span> <span class="p">{</span>
<span class="k">go</span> <span class="n">fun</span><span class="p">(</span><span class="n">ch</span><span class="p">,</span> <span class="o">&</span><span class="n">wg</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">go</span> <span class="k">func</span><span class="p">()</span> <span class="p">{</span>
<span class="n">wg</span><span class="o">.</span><span class="n">Wait</span><span class="p">()</span>
<span class="nb">close</span><span class="p">(</span><span class="n">ch</span><span class="p">)</span>
<span class="p">}()</span>
<span class="k">var</span> <span class="n">sum</span> <span class="kt">int</span>
<span class="k">for</span> <span class="n">result</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">ch</span> <span class="p">{</span>
<span class="n">sum</span> <span class="o">+=</span> <span class="n">result</span>
<span class="p">}</span>
<span class="n">fmt</span><span class="o">.</span><span class="n">Print</span><span class="p">(</span><span class="n">sum</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>So in the action, all you have to do is run and update the README periodically.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">name</span><span class="pi">:</span> <span class="s">Run</span>
<span class="na">on</span><span class="pi">:</span>
<span class="na">workflow_dispatch</span><span class="pi">:</span>
<span class="na">schedule</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">cron</span><span class="pi">:</span> <span class="s2">"</span><span class="s">0</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*</span><span class="nv"> </span><span class="s">*"</span>
<span class="na">permissions</span><span class="pi">:</span>
<span class="na">contents</span><span class="pi">:</span> <span class="s">write</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">run</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Checkout</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v4</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Setup Go</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">actions/setup-go@v4</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Update Markdown</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">salary=$(make run)</span>
<span class="s">cat > README.md <<EOF</span>
<span class="s">### Salary</span>
<span class="s">EOF</span>
<span class="s">printf "%s\n" "$salary" >> README.md</span>
<span class="na">env</span><span class="pi">:</span>
<span class="na">APILAYER_APIKEY</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">HUSKY_MONTHLY_SALARY</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">HUSKY_CURRENCY</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">TOGGL_WORKSPACE_ID</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">TOGGL_EMAIL</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">TOGGL_PASSWORD</span><span class="pi">:</span> <span class="s">$</span>
<span class="na">TOGGL_HOURLY_RATE</span><span class="pi">:</span> <span class="s">$</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Commit report</span>
<span class="na">run</span><span class="pi">:</span> <span class="pi">|</span>
<span class="s">git config --global user.name 'github-actions[bot]'</span>
<span class="s">git config --global user.email '41898282+github-actions[bot]@users.noreply.github.com'</span>
<span class="s">git add README.md</span>
<span class="s">git commit -am "Automated Salary Update" || true</span>
<span class="s">git push</span>
</code></pre></div></div>
<h2 id="blog">Blog</h2>
<p>The next step is to automate the publishing of new blog posts using ChatGPT ;-).</p>
2023-11-01T00:00:00+00:00
Rodrigo Delduca
-
Rodrigo Delduca: Working with compressed binary data on AWS IoT Core
https://nullonerror.org/2023/10/17/working-with-compressed-binary-data-on-aws-iot-core/
<h3 id="objective">Objective</h3>
<p>Today we will see how to send compressed CBOR with ZSTD from an ESP32 microcontroller through an MQTT topic, passing through AWS IoT Core, and finally being decoded in a TypeScript-written Lambda function.</p>
<h3 id="intro">Intro</h3>
<p>Firstly, what is a microcontroller (MCU)? In this article, we will be using the ESP32 microcontroller from Espressif. It is a highly affordable and inexpensive microcontroller that comes with built-in WiFi and Bluetooth, making it ideal for IoT projects as we will see today. It also boasts a generous amount of flash memory and RAM, as well as a powerful dual-core 32-bit CPU.</p>
<p>Another tool we will be using is PlatformIO, a development framework that functions as a series of plugins and command-line tools for VSCode. With PlatformIO, we have everything we need, from project setup, board selection, serial port speed, compilation flags (yes, we’ll be compiling code in C and C++), and more. Installing it is quite simple; just visit https://platformio.org/ and follow the instructions.</p>
<p>Lastly, to streamline our project and infrastructure, we will be using the Serverless Framework, a framework designed for developing serverless solutions. In our case, we will be using a Lambda function to receive messages sent to the topic. The Serverless Framework is a perfect fit for this scenario and works seamlessly with AWS Amazon.</p>
<h3 id="compressed-binary-data">Compressed Binary Data</h3>
<p>We could certainly use JSON. In fact, with JSON, it is possible to perform queries using the WHERE clause (Yes, IoT Core supports SQL, and we will delve into that later). However, our objective here is to save as many bytes as possible. Imagine that our application will send and receive data via a costly and unreliable telephone connection. Therefore, we need to compress the data as much as possible.</p>
<p>Firstly, let’s construct the raw payload in CBOR. CBOR is an interchangeable binary format specified in an RFC and supported by various programming languages (it is derived from MsgPack, in case you’ve heard of it before).</p>
<p>Since this part will be done on the microcontroller side, we will be using the C++ language with the <code class="language-plaintext highlighter-rouge">ssilverman/libCBOR</code> library. To do this, we open the <code class="language-plaintext highlighter-rouge">platformio.ini</code> file and add the dependency under <code class="language-plaintext highlighter-rouge">lib_deps</code>.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf"><CBOR.h></span><span class="cp">
#include</span> <span class="cpf"><CBOR_parsing.h></span><span class="cp">
#include</span> <span class="cpf"><CBOR_streams.h></span><span class="cp">
</span>
<span class="k">namespace</span> <span class="n">cbor</span> <span class="o">=</span> <span class="o">::</span><span class="n">qindesign</span><span class="o">::</span><span class="n">cbor</span><span class="p">;</span>
</code></pre></div></div>
<p>Since we are developing for a microcontroller, memory allocation is a critical concern as it can lead to fragmentation and other issues. Therefore, we will define a buffer of 256 bytes for reading and writing CBOR messages, which is more than sufficient for our current application.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">constexpr</span> <span class="kt">size_t</span> <span class="n">kBytesSize</span> <span class="o">=</span> <span class="mi">256</span><span class="p">;</span>
<span class="kt">uint8_t</span> <span class="n">bytes</span><span class="p">[</span><span class="n">kBytesSize</span><span class="p">]{</span><span class="mi">0</span><span class="p">};</span>
<span class="n">cbor</span><span class="o">::</span><span class="n">BytesStream</span> <span class="n">bs</span><span class="p">{</span><span class="n">bytes</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">bytes</span><span class="p">)};</span>
<span class="n">cbor</span><span class="o">::</span><span class="n">BytesPrint</span> <span class="n">bp</span><span class="p">{</span><span class="n">bytes</span><span class="p">,</span> <span class="k">sizeof</span><span class="p">(</span><span class="n">bytes</span><span class="p">)};</span>
</code></pre></div></div>
<p>Next, let’s prepare to use ZSTD. The steps are the same as with CBOR.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf"><zstd.h></span><span class="cp">
</span>
<span class="k">constexpr</span> <span class="kt">size_t</span> <span class="n">kZBytesSize</span> <span class="o">=</span> <span class="mi">256</span><span class="p">;</span>
<span class="kt">uint8_t</span> <span class="n">zbytes</span><span class="p">[</span><span class="n">kZBytesSize</span><span class="p">]{</span><span class="mi">0</span><span class="p">};</span>
</code></pre></div></div>
<p>Finally, and not least importantly, let’s prepare to send messages to a topic on IoT Core. The setup is a bit complex and requires attention. Firstly, we need to add the <code class="language-plaintext highlighter-rouge">knolleary/PubSubClient</code> library.</p>
<p>We will need three things from AWS IoT Core: the Certificate Authority (CA) certificate, the Thing certificate, and the private key certificate of the Thing. We will see how to create rules in IoT Core to allow things to subscribe and publish only to specific topics for security reasons. One more thing, in our project, we will hardcode these values in the flash memory of the ESP32 using the <em>PROGMEM</em> directive.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">#include</span> <span class="cpf"><WiFiClientSecure.h></span><span class="cp">
#include</span> <span class="cpf"><PubSubClient.h></span><span class="cp">
</span>
<span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">AWS_IOT_ENDPOINT</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"....amazonaws.com"</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="kt">uint16_t</span> <span class="n">AWS_IOT_PORT</span> <span class="o">=</span> <span class="mi">8883</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">THINGNAME</span><span class="p">[]</span> <span class="o">=</span> <span class="s">"My ESP32"</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">AWS_CERT_CA</span><span class="p">[]</span> <span class="n">PROGMEM</span> <span class="o">=</span> <span class="s">R"EOF(
-----BEGIN CERTIFICATE-----
... AWS Amazon Certificate Authority (CA)
-----END CERTIFICATE-----
)EOF"</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">AWS_CERT_CRT</span><span class="p">[]</span> <span class="n">PROGMEM</span> <span class="o">=</span> <span class="s">R"KEY(
-----BEGIN CERTIFICATE-----
... Thing's certificate
-----END CERTIFICATE-----
)KEY"</span><span class="p">;</span>
<span class="k">static</span> <span class="k">const</span> <span class="kt">char</span> <span class="n">AWS_CERT_PRIVATE</span><span class="p">[]</span> <span class="n">PROGMEM</span> <span class="o">=</span> <span class="s">R"KEY(
-----BEGIN RSA PRIVATE KEY-----
... Thing's private certificate
-----END RSA PRIVATE KEY-----
)KEY"</span><span class="p">;</span>
</code></pre></div></div>
<p>We need an instance of PubSub to publish and subscribe to topics, as well as a WiFi client to configure the aforementioned keys.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">WiFiClientSecure</span> <span class="n">net</span><span class="p">;</span>
<span class="n">PubSubClient</span> <span class="nf">pubsub</span><span class="p">(</span><span class="n">AWS_IOT_ENDPOINT</span><span class="p">,</span> <span class="n">AWS_IOT_PORT</span><span class="p">,</span> <span class="n">net</span><span class="p">);</span>
</code></pre></div></div>
<p>Now let’s proceed with a typical Arduino program structure, with the setup and loop functions. In the setup function, we will connect to the WiFi network, and then we will configure the keys in the WiFiClientSecure object so that the PubSubClient can use them to connect to IoT Core. Without these keys, the connection will be rejected by the AWS Amazon servers.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">setup</span><span class="p">()</span>
<span class="p">{</span>
<span class="c1">// For debugging.</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="mi">115200</span><span class="p">);</span>
<span class="c1">// Connect to WiFi.</span>
<span class="n">WiFi</span><span class="p">.</span><span class="n">mode</span><span class="p">(</span><span class="n">WIFI_STA</span><span class="p">);</span>
<span class="n">WiFi</span><span class="p">.</span><span class="n">begin</span><span class="p">(</span><span class="n">WIFI_SSID</span><span class="p">,</span> <span class="n">WIFI_PASSWORD</span><span class="p">);</span>
<span class="c1">// Wait for the connection.</span>
<span class="k">while</span> <span class="p">(</span><span class="n">WiFi</span><span class="p">.</span><span class="n">status</span><span class="p">()</span> <span class="o">!=</span> <span class="n">WL_CONNECTED</span><span class="p">)</span>
<span class="p">{</span>
<span class="n">delay</span><span class="p">(</span><span class="mi">500</span><span class="p">);</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"."</span><span class="p">);</span>
<span class="p">}</span>
<span class="c1">// Show WiFi information.</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">();</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"Connected to "</span><span class="p">);</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">WIFI_SSID</span><span class="p">);</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">print</span><span class="p">(</span><span class="s">"IP address: "</span><span class="p">);</span>
<span class="n">Serial</span><span class="p">.</span><span class="n">println</span><span class="p">(</span><span class="n">WiFi</span><span class="p">.</span><span class="n">localIP</span><span class="p">());</span>
<span class="c1">// Setup the certificates.</span>
<span class="n">net</span><span class="p">.</span><span class="n">setCACert</span><span class="p">(</span><span class="n">AWS_CERT_CA</span><span class="p">);</span>
<span class="n">net</span><span class="p">.</span><span class="n">setCertificate</span><span class="p">(</span><span class="n">AWS_CERT_CRT</span><span class="p">);</span>
<span class="n">net</span><span class="p">.</span><span class="n">setPrivateKey</span><span class="p">(</span><span class="n">AWS_CERT_PRIVATE</span><span class="p">);</span>
<span class="c1">// Connect to IoT Core.</span>
<span class="n">pubsub</span><span class="p">.</span><span class="n">connect</span><span class="p">(</span><span class="n">THINGNAME</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>If everything goes well, the <code class="language-plaintext highlighter-rouge">connect</code> method of PubSubClient will return <em>true</em>. We won’t perform that check; instead, we’ll attempt to publish directly and monitor the results on the IoT Core dashboard.</p>
<p>Now comes the interesting part. We will assemble our payload in CBOR format, compress it using ZSTD, and publish it to a topic.</p>
<div class="language-cpp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">on_sensor</span><span class="p">(</span><span class="k">const</span> <span class="kt">uint64_t</span> <span class="o">*</span><span class="n">sensor_data</span><span class="p">,</span> <span class="kt">size_t</span> <span class="n">sensor_data_size</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Create a new instance of the CBOR writer.</span>
<span class="n">cbor</span><span class="o">::</span><span class="n">Writer</span> <span class="n">cbor</span><span class="p">{</span><span class="n">bp</span><span class="p">};</span>
<span class="c1">// Reset BytesPrint instance.</span>
<span class="n">bp</span><span class="p">.</span><span class="n">reset</span><span class="p">();</span>
<span class="c1">// Indicates that what follows is an array of a certain size.</span>
<span class="n">cbor</span><span class="p">.</span><span class="n">beginArray</span><span class="p">(</span><span class="n">sensor_data_size</span><span class="p">);</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">size_t</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">sensor_data_size</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span>
<span class="p">{</span>
<span class="c1">// For each item in the array, write it to CBOR.</span>
<span class="n">cbor</span><span class="p">.</span><span class="n">writeUnsignedInt</span><span class="p">(</span><span class="n">sensor_data</span><span class="p">[</span><span class="n">i</span><span class="p">]);</span>
<span class="p">}</span>
<span class="c1">// Get the final CBOR size.</span>
<span class="k">const</span> <span class="kt">size_t</span> <span class="n">lenght</span> <span class="o">=</span> <span class="n">cbor</span><span class="p">.</span><span class="n">getWriteSize</span><span class="p">();</span>
<span class="c1">// Compress the CBOR buffer using ZSTD.</span>
<span class="kt">size_t</span> <span class="n">compressedSize</span> <span class="o">=</span> <span class="n">ZSTD_compress</span><span class="p">(</span><span class="n">zbytes</span><span class="p">,</span> <span class="n">kZBytesSize</span><span class="p">,</span> <span class="n">bytes</span><span class="p">,</span> <span class="n">lenght</span><span class="p">,</span> <span class="n">ZSTD_CLEVEL_DEFAULT</span><span class="p">);</span>
<span class="c1">// Publish the binary compressed data onto the topic.</span>
<span class="kt">char</span> <span class="n">topic</span><span class="p">[</span><span class="mi">128</span><span class="p">];</span>
<span class="n">sprintf</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="s">"sensors/%s/v1"</span><span class="p">,</span> <span class="n">THINGNAME</span><span class="p">);</span>
<span class="n">pubsub</span><span class="p">.</span><span class="n">publish</span><span class="p">(</span><span class="n">topic</span><span class="p">,</span> <span class="n">zbytes</span><span class="p">,</span> <span class="n">compressedSize</span><span class="p">,</span> <span class="nb">false</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="on-the-cloud-side">On the cloud side</h3>
<p>As I mentioned before, we will be using the Serverless Framework, which follows the Infrastructure as Code (IaC) approach. So what we’ll do is create the rules for the Things, create a lambda function, and define the trigger for that lambda as IoT Core. Since the data is in binary format, we will encode it in Base64 in the IoT Core SQL.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">service</span><span class="pi">:</span> <span class="s">myiot</span>
<span class="na">configValidationMode</span><span class="pi">:</span> <span class="s">error</span>
<span class="na">frameworkVersion</span><span class="pi">:</span> <span class="s2">"</span><span class="s">3"</span>
<span class="na">provider</span><span class="pi">:</span>
<span class="na">name</span><span class="pi">:</span> <span class="s">aws</span>
<span class="na">runtime</span><span class="pi">:</span> <span class="s">nodejs18.x</span>
<span class="na">architecture</span><span class="pi">:</span> <span class="s">arm64</span>
<span class="na">stage</span><span class="pi">:</span> <span class="s">development</span>
<span class="na">resources</span><span class="pi">:</span>
<span class="na">Resources</span><span class="pi">:</span>
<span class="na">IoTPolicy</span><span class="pi">:</span>
<span class="na">Type</span><span class="pi">:</span> <span class="s">AWS::IoT::Policy</span>
<span class="na">Properties</span><span class="pi">:</span>
<span class="na">PolicyName</span><span class="pi">:</span> <span class="s">IoTPolicy</span>
<span class="na">PolicyDocument</span><span class="pi">:</span>
<span class="na">Version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2012-10-17"</span>
<span class="na">Statement</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
<span class="na">Action</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">iot:Connect</span>
<span class="na">Resource</span><span class="pi">:</span> <span class="s">arn:aws:iot:*:*:client/\${iot:Connection.Thing.ThingName}</span>
<span class="pi">-</span> <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
<span class="na">Action</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">iot:Publish</span>
<span class="pi">-</span> <span class="s">iot:Receive</span>
<span class="na">Resource</span><span class="pi">:</span> <span class="s">arn:aws:iot:*:*:topic/*/\${iot:Connection.Thing.ThingName}/*</span>
<span class="pi">-</span> <span class="na">Effect</span><span class="pi">:</span> <span class="s">Allow</span>
<span class="na">Action</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">iot:Subscribe</span>
<span class="na">Resource</span><span class="pi">:</span> <span class="s">arn:aws:iot:*:*:topicfilter/*/\${iot:Connection.Thing.ThingName}/*</span>
<span class="na">functions</span><span class="pi">:</span>
<span class="na">tracker</span><span class="pi">:</span>
<span class="na">handler</span><span class="pi">:</span> <span class="s">app/mylambda.handler</span>
<span class="na">events</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">iot</span><span class="pi">:</span>
<span class="na">sql</span><span class="pi">:</span> <span class="s2">"</span><span class="s">SELECT</span><span class="nv"> </span><span class="s">timestamp()</span><span class="nv"> </span><span class="s">AS</span><span class="nv"> </span><span class="s">timestamp,</span><span class="nv"> </span><span class="s">topic()</span><span class="nv"> </span><span class="s">AS</span><span class="nv"> </span><span class="s">topic,</span><span class="nv"> </span><span class="s">encode(*,</span><span class="nv"> </span><span class="s">'base64')</span><span class="nv"> </span><span class="s">AS</span><span class="nv"> </span><span class="s">data</span><span class="nv"> </span><span class="s">FROM</span><span class="nv"> </span><span class="s">'sensors/+/v1'"</span>
<span class="na">sqlVersion</span><span class="pi">:</span> <span class="s2">"</span><span class="s">2016-03-23"</span>
<span class="na">plugins</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">serverless-plugin-typescript</span>
</code></pre></div></div>
<p>The YAML file for serverless may seem a bit intimidating at first, but it’s simple. It essentially does two things. Firstly, it creates an IoTPolicy for the things. A Thing can only publish or subscribe to its own topic with IoTPolicies, allowing for a granular level of security.</p>
<p>Also, it’s worth noting that we are using ARM64 architecture. Lambdas running on the ARM architecture are not only more cost-effective but also more efficient compared to x86_64.</p>
<p>The second part is the definition of the lambda function and its trigger. It uses a query in IoT Core, which is the key to working with binary data in IoT Core. You need to encode the data in <strong>base64</strong> before sending it to the lambda function; otherwise, it won’t work. This lambda function is “listening” to the sensors topic from any Thing, hence the plus symbol in the topic. By default, I prefer to version APIs, and a topic shouldn’t be an exception. Therefore, this is version 1 of my project.</p>
<p>Now let’s take a look at the lambda function itself. For this project, I chose to use TypeScript, but AWS Lambda and the Serverless Framework support various programming languages.</p>
<p>The code is quite straightforward. It receives the binary payload in the <code class="language-plaintext highlighter-rouge">data</code> parameter (as defined in the SQL statement above). First, it decodes the payload from base64 to binary. Then, it decompresses it using ZSTD and, finally, utilizes the CBOR library to parse it into a JavaScript object, ready to be used.</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">import</span> <span class="p">{</span> <span class="nx">decompressSync</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">@skhaz/zstd</span><span class="dl">"</span><span class="p">;</span>
<span class="k">import</span> <span class="p">{</span> <span class="nx">decodeFirstSync</span> <span class="p">}</span> <span class="k">from</span> <span class="dl">"</span><span class="s2">cbor</span><span class="dl">"</span><span class="p">;</span>
<span class="k">export</span> <span class="k">async</span> <span class="kd">function</span> <span class="nx">handler</span><span class="p">(</span><span class="nx">event</span><span class="p">:</span> <span class="p">{</span> <span class="nl">timestamp</span><span class="p">:</span> <span class="kr">number</span><span class="p">;</span> <span class="nl">topic</span><span class="p">:</span> <span class="kr">string</span><span class="p">;</span> <span class="nl">data</span><span class="p">:</span> <span class="kr">string</span> <span class="p">})</span> <span class="p">{</span>
<span class="c1">// Extract from event some variables.</span>
<span class="kd">const</span> <span class="p">{</span> <span class="nx">timestamp</span><span class="p">,</span> <span class="nx">topic</span><span class="p">,</span> <span class="nx">data</span> <span class="p">}</span> <span class="o">=</span> <span class="nx">event</span><span class="p">;</span>
<span class="c1">// Decode from base64 to binary.</span>
<span class="kd">const</span> <span class="nx">buffer</span> <span class="o">=</span> <span class="nx">Buffer</span><span class="p">.</span><span class="k">from</span><span class="p">(</span><span class="nx">data</span><span class="p">,</span> <span class="dl">"</span><span class="s2">base64</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// Decompress using ZSTD algorithm.</span>
<span class="kd">const</span> <span class="nx">cbor</span> <span class="o">=</span> <span class="nx">decompressSync</span><span class="p">(</span><span class="nx">buffer</span><span class="p">);</span>
<span class="c1">// Parse the binary CBOR to JavaScript object.</span>
<span class="kd">const</span> <span class="nx">payload</span> <span class="o">=</span> <span class="nx">decodeFirstSync</span><span class="p">(</span><span class="nx">cbor</span><span class="p">);</span>
<span class="c1">// Print the sensor array data.</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">payload</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="conclusion">Conclusion</h3>
<p>In conclusion, it is possible to save a few bytes or even more with these techniques while utilizing cloud solutions like IoT Core in your projects. It allows for efficient data handling and enables the integration of cloud services into your applications.</p>
<p>The compression ratio can vary dramatically, depending solely on the input. In my experiments with numerical data, it ranged from around 30% to 40%.</p>
2023-10-17T00:00:00+00:00
Rodrigo Delduca
-
Thiago Avelino: Gentlemen’s Club vs Strip Club: Is There Really a Difference?
https://podcasters.spotify.com/pod/show/strip-clubs-las-vegas/episodes/Gentlemens-Club-vs-Strip-Club-Is-There-Really-a-Difference-e27337q
<p>Confused about the distinctions between Strip Clubs and Gentlemen&#39;s Clubs in Las Vegas? In this episode, we&#39;re going to dissect what sets these venues apart and what you can anticipate from the very best of both. Be sure to listen in as we demystify the Las Vegas club scene.</p>
<p><br></p>
<p><a href="https://www.stripclubconcierge.com/" target="_blank" rel="noopener noreferer">https://www.stripclubconcierge.com/</a></p>
<p><br></p>
<p><a href="https://www.stripclubconcierge.com/gentlemens-club-vs-strip-club/" target="_blank" rel="noopener noreferer">https://www.stripclubconcierge.com/gentlemens-club-vs-strip-club/</a></p>
<p><br></p>
<p>Tags: Gentlemens Clubs Las Vegas, Best Gentlemens Clubs Las Vegas, Treasures Gentlemen’s Club, Crazy Horse III Gentlemen’s Club</p>
2023-08-15T13:21:00+00:00
-
Thiago Avelino: Las Vegas Strippers 101: Your 2023 Guide to "Strippers to the Room" (Private Bachelor Party Bookings)
https://podcasters.spotify.com/pod/show/strip-clubs-las-vegas/episodes/Las-Vegas-Strippers-101-Your-2023-Guide-to-Strippers-to-the-Room-Private-Bachelor-Party-Bookings-e273351
<p>Are you mulling over hiring Las Vegas strippers for a bachelor party? In this episode, we help you weigh up the pros and cons between opting for private in-room entertainers and taking the gang to a strip club. Make sure you tune in to get all the information you need for planning the ultimate bachelor party.</p>
<p><br></p>
<p><a href="https://www.stripclubconcierge.com/" target="_blank" rel="noopener noreferer">https://www.stripclubconcierge.com/</a></p>
<p><br></p>
<p><a href="https://www.stripclubconcierge.com/las-vegas-strippers/" target="_blank" rel="noopener noreferer">https://www.stripclubconcierge.com/las-vegas-strippers/</a></p>
<p><br></p>
<p>Tags: Las Vegas Bachelor Party, Las Vegas Guides, Las Vegas Strippers, Outcall Strippers, Private Strippers</p>
2023-08-12T13:18:00+00:00
-
Ellison Leão: Gerando documentação vimdoc de plugins Neovim com Github Actions
https://dev.to/ellisonleao/gerando-documentacao-vimdoc-de-plugins-neovim-com-github-actions-22if
<p><em>Dificuldade: intermediário</em></p>
<p>Escrever documentação nem sempre é uma das tarefas mais prazerosas, mas ter uma boa documentação nos nossos projetos é o que realmente faz diferença. Ainda mais quando usamos ferramentas que nos auxiliam nessa escrita.</p>
<p>Nesse post vamos ensinar como automatizar a geração da documentação específica para plugins <strong>Neovim</strong> utilizando <strong>Github Actions</strong></p>
<h2>
Entendendo o fluxo
</h2>
<p>Assumindo que entendemos o fluxo básico das <strong>Github Actions</strong>, temos o seguinte fluxo para a geração da doc:</p>
<ol>
<li>Fazemos um push na branch principal (geralmente chamada de <code>main</code>)</li>
<li>Um job chamado <code>docs</code> é iniciado com os seguintes passos:
<ul>
<li>Conversão do <code>README.md</code> do seu projeto em um arquivo .txt no formato <a href="https://vimdoc.sourceforge.net/htmldoc/helphelp.html#help-writing" rel="noopener noreferrer">vimdoc</a>
</li>
<li>Criamos um commit e fazemos o push na mesma branch com o resultado da conversão</li>
</ul>
</li>
</ol>
<p>Convertendo isso para o <strong>Github Actions</strong>, temos:</p>
<ol>
<li>Crie um arquivo <code>.github/workflows/docs.yml</code> com o seguinte conteúdo:
</li>
</ol>
<div class="highlight js-code-highlight">
<pre class="highlight yaml"><code><span class="na">on</span><span class="pi">:</span>
<span class="na">push</span><span class="pi">:</span>
<span class="na">branches</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">main</span> <span class="c1"># aqui nossa branch principal se chama `main`</span>
<span class="na">jobs</span><span class="pi">:</span>
<span class="na">docs</span><span class="pi">:</span>
<span class="na">runs-on</span><span class="pi">:</span> <span class="s">ubuntu-latest</span>
<span class="na">needs</span><span class="pi">:</span> <span class="s">test</span>
<span class="na">steps</span><span class="pi">:</span>
<span class="pi">-</span> <span class="na">uses</span><span class="pi">:</span> <span class="s">actions/checkout@v3</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">panvimdoc</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">kdheepak/panvimdoc@main</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">vimdoc</span><span class="pi">:</span> <span class="s">nome-do-seu-projeto</span> <span class="c1"># sem extensão .txt</span>
<span class="na">version</span><span class="pi">:</span> <span class="s2">"</span><span class="s">Neovim</span><span class="nv"> </span><span class="s">>=</span><span class="nv"> </span><span class="s">0.8.0"</span>
<span class="na">demojify</span><span class="pi">:</span> <span class="kc">true</span> <span class="c1"># coloque false caso queira manter os emojis</span>
<span class="na">treesitter</span><span class="pi">:</span> <span class="kc">true</span>
<span class="pi">-</span> <span class="na">name</span><span class="pi">:</span> <span class="s">Push changes</span>
<span class="na">uses</span><span class="pi">:</span> <span class="s">stefanzweifel/git-auto-commit-action@v4</span>
<span class="na">with</span><span class="pi">:</span>
<span class="na">commit_message</span><span class="pi">:</span> <span class="s2">"</span><span class="s">auto-generate</span><span class="nv"> </span><span class="s">vimdoc"</span>
<span class="na">commit_user_name</span><span class="pi">:</span> <span class="s2">"</span><span class="s">github-actions[bot]"</span>
<span class="na">commit_user_email</span><span class="pi">:</span> <span class="s2">"</span><span class="s">github-actions[bot]@users.noreply.github.com"</span>
<span class="na">commit_author</span><span class="pi">:</span> <span class="s2">"</span><span class="s">github-actions[bot]</span><span class="nv"> </span><span class="s"><github-actions[bot]@users.noreply.github.com>"</span>
</code></pre>
</div>
<h2>
Preparando seu README
</h2>
<p>Para um bom resultado de uma documentação vimdoc, recomendamos que seu README tenha pelo menos:</p>
<ul>
<li>Uma seção <code>Sobre</code>
</li>
<li>Uma seção <code>Instalação</code>
</li>
<li>Uma seção <code>Como usar</code>
</li>
</ul>
<p>O uso de tabelas, bullet points também é suportado e recomendado. Quanto mais detalhes conseguir colocar na documentação, melhor ficará o resultado (até emojis são suportados!)</p>
<h2>
Colocando tudo para funcionar
</h2>
<p>Antes de commitar e rodar a action pela primeira vez, crie o arquivo vazio da doc:<br />
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nv">$ </span><span class="nb">touch </span>doc/nome-do-seu-projeto.txt
<span class="nv">$ </span>git add doc/nome-do-seu-projeto.txt
<span class="nv">$ </span>git commit <span class="nt">-m</span> <span class="s1">'adicionando arquivo vimdoc'</span>
<span class="nv">$ </span>git push
</code></pre>
</div>
<p>Agora envie o novo worflow:<br />
</p>
<div class="highlight js-code-highlight">
<pre class="highlight shell"><code><span class="nv">$ </span>git add .github/workflows/docs.yml
<span class="nv">$ </span>git commit <span class="nt">-m</span> <span class="s1">'adicionando docs workflow'</span>
<span class="nv">$ </span>git push
</code></pre>
</div>
<p>Se tudo deu certo, você já poderá ver o resultado do workflow com seu vimdoc gerado na branch principal. Exemplo do resultado na imagem abaixo</p>
<p><a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pre86sg64pzp5y69bdg.png" class="article-body-image-wrapper"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5pre86sg64pzp5y69bdg.png" alt="resultado do job" width="800" height="377" /></a></p>
<p>Exemplo pode ser visto <a href="https://github.com/ellisonleao/carbon-now.nvim/actions/runs/5673377787" rel="noopener noreferrer">aqui</a></p>
<h2>
Links úteis
</h2>
<ul>
<li>Documentação da <a href="https://kdheepak.com/panvimdoc/" rel="noopener noreferrer">github action</a>
</li>
<li>Exemplo de um bom <a href="https://github.com/folke/lazy.nvim/blob/main/README.md" rel="noopener noreferrer">README</a> vs o <a href="https://github.com/folke/lazy.nvim/blob/main/doc/lazy.nvim.txt" rel="noopener noreferrer">resultado</a> em vimdoc</li>
</ul>
<p>Se gostou do artigo, não esqueça de compartilhar em outras redes e aproveita e me segue também! <a href="https://linktr.ee/ellisonleao" rel="noopener noreferrer">https://linktr.ee/ellisonleao</a></p>
2023-07-26T20:40:59+00:00
-
Blog do PauloHRPinheiro: Debugando em Python
https://paulohrpinheiro.xyz/texts/python/2022-12-17-debugando-em-python.html
Iniciando a usar o módulo pdb do Python para debugar com maestria, na linha de comando
2022-12-17T00:00:00+00:00
-
PythonClub: Questões para estudo de algoritmos
https://pythonclub.com.br/questoes-para-estudo-de-algoritmos.html
<p>Recentemente li o texto do Maycon Alves, <a href="https://mayconbalves.com.br/3-algoritmos-para-voc%C3%AA-sua-l%C3%B3gica/">"3 algoritmos para você sua lógica"</a>, onde são apresentados 3 problemas para treinar a lógica e escrita de algoritmos: <a href="https://pt.wikipedia.org/wiki/Fatorial">cálculo de fatorial</a>, <a href="https://pt.wikipedia.org/wiki/N%C3%BAmero_primo">identificar se um número é primo</a>, e <a href="https://pt.wikipedia.org/wiki/Sequ%C3%AAncia_de_Fibonacci">calcular os valores da sequência de Fibonacci</a>. São problemas interessantes, e após resolvê-los, pode-se fazer outras perguntas que levam a um entendimento mais profundo desses algoritmos. Eu recomendo que leiam o texto do Maycon primeiro e tentem implementar uma solução para esses problemas propostos, e com isso feito, vamos discutir um pouco sobre eles.</p>
<h2>Analisando as soluções</h2>
<p>No texto do Maycon, tem uma dica sobre o problema da sequência de Fibonacci, onde é dito que ele pode ser resolvido usando recursividade ou <em>loops</em>. Vamos analisar essas opções.</p>
<p>Uma solução recursiva pode ser implementada como a baixo. O código dessa solução é simples e se aproxima bastante da descrição matemática do problema.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="k">if</span> <span class="n">n</span> <span class="o"><=</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">return</span> <span class="n">n</span>
<span class="k">return</span> <span class="n">fibonacci</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">2</span><span class="p">)</span> <span class="o">+</span> <span class="n">fibonacci</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</pre></div>
<p>Enquanto uma solução iterativa (com <em>loop</em>) pode ser um pouco mais complicada de se ler:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">fibonacci</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="n">a</span> <span class="o">=</span> <span class="mi">0</span>
<span class="n">b</span> <span class="o">=</span> <span class="mi">1</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="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
<span class="n">a</span><span class="p">,</span> <span class="n">b</span> <span class="o">=</span> <span class="n">b</span><span class="p">,</span> <span class="n">a</span> <span class="o">+</span> <span class="n">b</span>
<span class="k">return</span> <span class="n">b</span>
</pre></div>
<p>Essas mesmas técnicas podem ser utilizadas para resolver o cálculo do fatorial. Onde uma implementação recursiva e outra iterativa podem ser vistas a baixo:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">fatorial</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="k">if</span> <span class="n">n</span> <span class="o"><=</span> <span class="mi">1</span><span class="p">:</span>
<span class="k">return</span> <span class="mi">1</span>
<span class="k">return</span> <span class="n">n</span> <span class="o">*</span> <span class="n">fatorial</span><span class="p">(</span><span class="n">n</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">fatorial</span><span class="p">(</span><span class="n">n</span><span class="p">):</span>
<span class="n">valor</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">1</span><span class="p">,</span> <span class="n">n</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span>
<span class="n">valor</span> <span class="o">*=</span> <span class="n">i</span>
<span class="k">return</span> <span class="n">valor</span>
</pre></div>
<p>Com essas soluções implementadas, vem uma pergunta: Existe alguma diferença entre elas, além da diferença de como isso está expresso no código? Um primeiro teste que pode ser feito é de desempenho visando observar quanto tempo cada implementação leva para calcular a resposta. Os testes foram executados em um notebook com processador Intel Core i7-6500U CPU @ 2.50GHz, memória RAM DDR4 2133MHz, no Python 3.9.2 do Debian 11, desativamente o <em>garbage collector</em> do Python durante a execução das funções para ter um resultado com menos variação, apresentados como uma média de 10 execuções (<a href="https://github.com/eduardoklosowski/blog/tree/main/content/2022-10-17-questoes-para-estudo-de-algoritmos">código utilizado</a>).</p>
<p>O gráfico a baixo mostra o tempo de execução das implementações que calculam os valores da sequência de Fibonacci, onde é possível observar que a implementação iterativa mantém uma linearidade do tempo conforme vai se pedindo números maiores da sequência, diferente da implementação recursiva, que a cada valor quase dobra o tempo de execução.</p>
<p><img alt="Gráfico do tempo de execução Fibonacci" src="https://pythonclub.com.br/feeds/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fibonacci.png" /></p>
<p>E a baixo pode-se observar o gráfico para as implementações que calculam o fatorial, que devido aos tempos serem mais baixos, possui uma variação um pouco maior, e é possível observar uma tendência de reta para as duas implementações, com a implementação recursiva tendo um ângulo um pouco mais íngreme, implicando em um algoritmo mais lento.</p>
<p><img alt="Gráfico do tempo de execução fatorial" src="https://pythonclub.com.br/feeds/images/eduardoklosowski/questoes-para-estudo-de-algoritmos/fatorial.png" /></p>
<p>A partir desses dois gráficos algumas perguntas podem ser feitas: Por que a implementação recursiva do Fibonacci apresentou uma curva que se aproxima de uma exponencial e não de uma reta como as demais? Qual a diferença para a implementação recursiva do fatorial que explicar isso? Implementações recursivas são sempre piores que as implementações iterativas, ou existem casos em elas superem ou se equivalem as iterativas?</p>
<p>Saindo um pouco desses gráficos, outras questões podem ser levantadas, como: Existe mais algum aspecto ou característica além do tempo de execução (e facilidade de leitura do código) para a escolha entre uma implementação ou outra? Considerando que essas funções vão rodar em um servidor, existe alguma coisa que possa ser feita para melhorar a performance dessas funções, como reaproveitar resultados já calculados para se calcular novos resultados? Como isso poderia ser feito? Quais são as desvantagens dessa abordagem?</p>
<p>Olhando para o problema de verificar se um número é primo, existe o <a href="https://pt.wikipedia.org/wiki/Crivo_de_Erat%C3%B3stenes">crivo de Eratóstenes</a>, ele é uma implementação eficiente? Existem casos em que ele pode ser uma boa solução ou não? O exemplo a baixo (retirado da Wikipédia) mostra o processo para encontrar todos os números primos até 120, existe alguma forma de adaptá-lo para executar diversas vezes reaproveitando o que já foi calculado, como sempre retornar o próximo número primo?</p>
<p><img alt="Exemplo do crivo de Eratóstenes" src="https://upload.wikimedia.org/wikipedia/commons/8/8c/New_Animation_Sieve_of_Eratosthenes.gif" /></p>
<h2>Considerações</h2>
<p>Se você nunca se deparou com perguntas desse tipo, seja bem-vindo a área de análise de algoritmos, onde após se ter uma solução, busca-se analisar e descrever o comportamento do algoritmo, e até a busca de algoritmos mais eficientes. E trazendo para o dia a dia de um desenvolvedor, essas questões podem ser a resposta do motivo do código funcionar muito bem no computador de quem desenvolveu, mas demorar muito ou apresentar problemas para rodar no servidor de produção, ou com o tempo (e crescimento do volume de dados) começar a dar problemas.</p>
<p>Nesse artigo eu apenas levantei as perguntas, deixo que cada um busque as respostas, que existem. Sintam-se livres para me procurar para discutir alguma questão ou orientações para encontrar as respostas, seja nos comentários do texto no <a href="https://dev.to/eduardoklosowski/questoes-para-estudo-de-algoritmos-5dab">dev.to</a> ou no <a href="https://twitter.com/eduklosowski">Twitter</a>.</p>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2022-10-17T18:00:00+00:00
Eduardo Klosowski
-
Ellison Leão: Meus 10 plugins favoritos para o Neovim em 2022
https://dev.to/ellisonleao/meus-10-plugins-favoritos-para-o-neovim-em-2022-43g2
<blockquote>
<p>Uma breve lista dos melhores plugins pra usar agora e aumentar sua produtividade</p>
</blockquote>
<p>Quando comecei a usar o Neovim, lá por 2017, depois de passar 6 anos usando Vim, não tinha sentido uma diferença tão grande porque primeiro a API lua era praticamente inexistente e minha configuração ainda era puramente vimscript e meus plugins também. </p>
<p>Mas tudo mudou quando fiz o upgrade pras versões 0.4.x e gradualmente comecei a me aventurar no mundo do Lua, no final de 2019. De lá pra ca já consegui migrar minhas configurações 100% para Lua e 99% dos plugins que uso hoje também são feitos na linguagem. Aqui vai minha lista dos 10 melhores plugins que acho indispensáveis hoje:</p>
<h2>
#10
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/wbthomason" rel="noopener noreferrer">
wbthomason
</a> / <a href="https://github.com/wbthomason/packer.nvim" rel="noopener noreferrer">
packer.nvim
</a>
</h2>
<h3>
A use-package inspired plugin manager for Neovim. Uses native packages, supports Luarocks dependencies, written in Lua, allows for expressive config
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<p><strong>NOTICE:</strong></p>
<p>This repository is currently unmaintained. For the time being (as of August, 2023), it is recommended to use one of the following plugin managers instead:</p>
<ul>
<li>
<a href="https://github.com/folke/lazy.nvim" rel="noopener noreferrer">lazy.nvim</a>: Most stable and maintained plugin manager for Nvim.</li>
<li>
<a href="https://github.com/lewis6991/pckr.nvim" rel="noopener noreferrer">pckr.nvim</a>: Spiritual successor of packer.nvim. Functional but not as stable as lazy.nvim.</li>
</ul>
<div class="markdown-heading">
<h1 class="heading-element">packer.nvim</h1>
</div>
<p><a href="https://gitter.im/packer-nvim/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge" rel="nofollow noopener noreferrer"><img src="https://camo.githubusercontent.com/335c9ee0b540d19a29323d900502be99a012414abdc965f6e4bd7047842f6541/68747470733a2f2f6261646765732e6769747465722e696d2f7061636b65722d6e76696d2f636f6d6d756e6974792e737667" alt="Gitter" /></a></p>
<p><a href="https://github.com/jwiegley/use-package" rel="noopener noreferrer"><code>use-package</code></a> inspired plugin/package management for
Neovim.</p>
<p>Have questions? Start a <a href="https://github.com/wbthomason/packer.nvim/discussions" rel="noopener noreferrer">discussion</a>.</p>
<p>Have a problem or idea? Make an <a href="https://github.com/wbthomason/packer.nvim/issues" rel="noopener noreferrer">issue</a> or a <a href="https://github.com/wbthomason/packer.nvim/pulls" rel="noopener noreferrer">PR</a>.</p>
<p><strong>Packer is built on native packages. You may wish to read <code>:h packages</code> before continuing</strong></p>
<div class="markdown-heading">
<h2 class="heading-element">Table of Contents</h2>
</div>
<ol>
<li><a href="https://github.com/wbthomason/packer.nvim#features" rel="noopener noreferrer">Features</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#requirements" rel="noopener noreferrer">Requirements</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#quickstart" rel="noopener noreferrer">Quickstart</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#bootstrapping" rel="noopener noreferrer">Bootstrapping</a></li>
<li>
<a href="https://github.com/wbthomason/packer.nvim#usage" rel="noopener noreferrer">Usage</a>
<ol>
<li><a href="https://github.com/wbthomason/packer.nvim#the-startup-function" rel="noopener noreferrer">The startup function</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#custom-initialization" rel="noopener noreferrer">Custom Initialization</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#specifying-plugins" rel="noopener noreferrer">Specifying Plugins</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#performing-plugin-management-operations" rel="noopener noreferrer">Performing plugin management operations</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#extending-packer" rel="noopener noreferrer">Extending packer</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#compiling-lazy-loaders" rel="noopener noreferrer">Compiling Lazy-Loaders</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#user-autocommands" rel="noopener noreferrer">User autocommands</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#using-a-floating-window" rel="noopener noreferrer">Using a floating window</a></li>
</ol>
</li>
<li><a href="https://github.com/wbthomason/packer.nvim#profiling" rel="noopener noreferrer">Profiling</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#debugging" rel="noopener noreferrer">Debugging</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#compatibility-and-known-issues" rel="noopener noreferrer">Compatibility and known issues</a></li>
<li><a href="https://github.com/wbthomason/packer.nvim#contributors" rel="noopener noreferrer">Contributors</a></li>
</ol>
<div class="markdown-heading">
<h2 class="heading-element">Features</h2>
</div>
<ul>
<li>Declarative plugin specification</li>
<li>Support for dependencies</li>
<li>Support for Luarocks dependencies</li>
<li>Expressive configuration and lazy-loading options</li>
<li>Automatically compiles efficient lazy-loading code to improve startup time</li>
<li>…</li>
</ul>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/wbthomason/packer.nvim" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>Começando por esse incrível gerenciador de plugins, que vai deixar sua vida bem mais fácil na hora de instalar, atualizar e configurar seus plugins, principalmente se você utiliza lua nas suas configurações. Tem suporte a instalação de pacotes do luarocks, <em>lazy loading</em> e mais.</p>
<h2>
#9
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/numToStr" rel="noopener noreferrer">
numToStr
</a> / <a href="https://github.com/numToStr/Comment.nvim" rel="noopener noreferrer">
Comment.nvim
</a>
</h2>
<h3>
🧠 💪 // Smart and powerful comment plugin for neovim. Supports treesitter, dot repeat, left-right/up-down motions, hooks, and more
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<div class="markdown-heading">
<h1 class="heading-element">// Comment.nvim </h1>
</div>
<p><sup>⚡ Smart and Powerful commenting plugin for neovim ⚡</sup></p>
<p><a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/42532967/136532939-926a8350-84b7-4e78-b045-fe21b5947388.gif"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F42532967%2F136532939-926a8350-84b7-4e78-b045-fe21b5947388.gif" alt="Comment.nvim" title="Commenting go brrrr" /></a></p>
<div class="markdown-heading">
<h3 class="heading-element">✨ Features</h3>
</div>
<ul>
<li>Supports treesitter. <a href="https://github.com/numToStr/Comment.nvim#treesitter" rel="noopener noreferrer">Read more</a>
</li>
<li>Supports <code>commentstring</code>. Read <code>:h comment.commentstring</code>
</li>
<li>Supports line (<code>//</code>) and block (<code>/* */</code>) comments</li>
<li>Dot (<code>.</code>) repeat support for <code>gcc</code>, <code>gbc</code> and friends</li>
<li>Count support for <code>[count]gcc</code> and <code>[count]gbc</code>
</li>
<li>Left-right (<code>gcw</code> <code>gc$</code>) and Up-Down (<code>gc2j</code> <code>gc4k</code>) motions</li>
<li>Use with text-objects (<code>gci{</code> <code>gbat</code>)</li>
<li>Supports pre and post hooks</li>
<li>Ignore certain lines, powered by Lua regex</li>
</ul>
<div class="markdown-heading">
<h3 class="heading-element">🚀 Installation</h3>
</div>
<ul>
<li>With <a href="https://github.com/folke/lazy.nvim" rel="noopener noreferrer">lazy.nvim</a>
</li>
</ul>
<div class="highlight highlight-source-lua notranslate position-relative overflow-auto js-code-highlight">
<pre><span class="pl-c"><span class="pl-c">--</span> add this to your lua/plugins.lua, lua/plugins/init.lua, or the file you keep your other plugins:</span>
{
<span class="pl-s"><span class="pl-pds">'</span>numToStr/Comment.nvim<span class="pl-pds">'</span></span>,
<span class="pl-smi">opts</span> <span class="pl-k">=</span> {
<span class="pl-c"><span class="pl-c">--</span> add any options here</span>
}
}
</pre>
</div>
<ul>
<li>With <a href="https://github.com/wbthomason/packer.nvim" rel="noopener noreferrer">packer.nvim</a>
</li>
</ul>
<div class="highlight highlight-source-lua notranslate position-relative overflow-auto js-code-highlight">
<pre><span class="pl-c1">use</span> {
<span class="pl-s"><span class="pl-pds">'</span>numToStr/Comment.nvim<span class="pl-pds">'</span></span>,
<span class="pl-en">config</span> <span class="pl-k">=</span> <span class="pl-k">function</span>()
<span class="pl-c1">require</span>(<span class="pl-s"><span class="pl-pds">'</span>Comment<span class="pl-pds">'</span></span>).<span class="pl-c1">setup</span>()
<span class="pl-k">end</span>
}</pre>
</div>
<ul>
<li>With <a href="https://github.com/junegunn/vim-plug" rel="noopener noreferrer">vim-plug</a>
</li>
</ul>
<div class="highlight highlight-source-viml notranslate position-relative overflow-auto js-code-highlight">
<pre>Plug <span class="pl-s"><span class="pl-pds">'</span>numToStr/Comment.nvim<span class="pl-pds">'</span></span>
<span class="pl-c"><span class="pl-c">"</span> Somewhere after plug#end()</span></pre>…
</div>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/numToStr/Comment.nvim" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>Um poderoso plugin para inserir e remover comentários em códigos. Tem suporte ao treesitter, que vamos falar mais pra frente. </p>
<h2>
#8
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/neovim" rel="noopener noreferrer">
neovim
</a> / <a href="https://github.com/neovim/nvim-lspconfig" rel="noopener noreferrer">
nvim-lspconfig
</a>
</h2>
<h3>
Quickstart configs for Nvim LSP
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<div class="markdown-heading">
<h1 class="heading-element">nvim-lspconfig</h1>
</div>
<p>nvim-lspconfig is a "data only" repo, providing basic, default <a href="https://neovim.io/doc/user/lsp.html" rel="nofollow noopener noreferrer">Nvim LSP client</a>
configurations for various LSP servers.</p>
<p>View <a href="https://github.com/neovim/nvim-lspconfigdoc/configs.md" rel="noopener noreferrer">all configs</a> or <code>:help lspconfig-all</code> from Nvim.</p>
<div class="markdown-heading">
<h2 class="heading-element">Important ⚠️
</h2>
</div>
<ul>
<li>These configs are <strong>best-effort and supported by the community (you).</strong> See <a href="https://github.com/neovim/nvim-lspconfig#contributions" rel="noopener noreferrer">contributions</a>.</li>
<li>If you found a bug in Nvim LSP (<code>:help lsp</code>), <a href="https://github.com/neovim/neovim/issues/new?assignees=&labels=bug%2Clsp&template=lsp_bug_report.yml" rel="noopener noreferrer">report it to Neovim core</a>
<ul>
<li>
<strong>Do not</strong> report it here. Only configuration data lives here.</li>
</ul>
</li>
<li>This repo only provides <em>configurations</em>. Its programmatic API is deprecated and must not be used externally.
<ul>
<li>The "framework" parts (<em>not</em> the configs) of nvim-lspconfig <a href="https://github.com/neovim/neovim/issues/28479" rel="noopener noreferrer">will be upstreamed to Nvim core</a>.</li>
</ul>
</li>
</ul>
<div class="markdown-heading">
<h2 class="heading-element">Install</h2>
</div>
<p><a href="https://luarocks.org/modules/neovim/nvim-lspconfig" rel="nofollow noopener noreferrer"><img src="https://camo.githubusercontent.com/57aa64ed10a79399509246a1ae220f761b1c35e4d44c6c07595faf4463019fd8/68747470733a2f2f696d672e736869656c64732e696f2f6c7561726f636b732f762f6e656f76696d2f6e76696d2d6c7370636f6e6669673f6c6f676f3d6c756126636f6c6f723d707572706c65" alt="LuaRocks" /></a></p>
<ul>
<li>Requires Nvim 0.10 above. Update Nvim and nvim-lspconfig before reporting an issue.</li>
<li>Install nvim-lspconfig using Vim's "packages" feature:
<div class="snippet-clipboard-content notranslate position-relative overflow-auto"><pre class="notranslate"><code>git clone https://github.com/neovim/nvim-lspconfig ~/.config/nvim/pack/nvim/start/nvim-lspconfig
</code></pre></div>
</li>
<li>Or use a 3rd-party plugin manager (consult the documentation for your plugin manager).</li>
</ul>
<div class="markdown-heading">
<h2 class="heading-element">Quickstart</h2>
</div>
<ol>
<li>Install a language server, e.g. <a href="https://github.com/neovim/nvim-lspconfigdoc/configs.md#pyright" rel="noopener noreferrer">pyright</a>
<div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight">
<pre>npm i -g pyright</pre>
</div>
</li>
<li>Add…</li>
</ol>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/neovim/nvim-lspconfig" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>Plugin obrigatório para quem quer utilizar o maravilhoso cliente nativo LSP. Com esse plugin a configuração dos LSP servers das linguagens fica extremamente fácil.</p>
<h2>
#7
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/williamboman" rel="noopener noreferrer">
williamboman
</a> / <a href="https://github.com/williamboman/nvim-lsp-installer" rel="noopener noreferrer">
nvim-lsp-installer
</a>
</h2>
<h3>
Further development has moved to https://github.com/williamboman/mason.nvim!
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<div class="markdown-heading">
<h2 class="heading-element">ℹ️ Project status</h2>
</div>
<blockquote>
<p>This is an excerpt from the <a href="https://github.com/williamboman/nvim-lsp-installer/discussions/876" rel="noopener noreferrer">announcement here</a>.</p>
</blockquote>
<p><em><code>nvim-lsp-installer</code> is no longer maintained.</em></p>
<p><a href="https://github.com/williamboman/mason.nvim" rel="noopener noreferrer"><code>mason.nvim</code></a> is the next generation version of <code>nvim-lsp-installer</code>. It
builds on top of the very same foundation as <code>nvim-lsp-installer</code> (which means it's easy to migrate), but with a
majority of internals refactored to improve extensibility and testability.</p>
<p>More importantly, the scope of <code>mason.nvim</code> has also been widened to target more than just LSP servers. <code>mason.nvim</code>
supports DAP servers, linters, formatters, and more. As of writing, <code>mason.nvim</code> provides 150+ packages for 100+
languages. It can be thought of as a general-purpose package manager, native to Neovim, that runs everywhere Neovim runs
(Windows, macOS, Linux, etc.).</p>
<p>Another big change with <code>mason.nvim</code> is that executables are now linked to a single, shared, location, allowing seamless
access from Neovim builtins (shell, terminal, etc.) as well as other 3rd party plugins.</p>
<p>There have also been other improvements…</p>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/williamboman/nvim-lsp-installer" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>Plugin que fornece todo suporte para uma rápida instalação dos LSP servers com uma excelente UX. É um plugin que funciona em conjunto com o <strong>nvim-lspconfig</strong></p>
<h2>
#6
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/jose-elias-alvarez" rel="noopener noreferrer">
jose-elias-alvarez
</a> / <a href="https://github.com/jose-elias-alvarez/null-ls.nvim" rel="noopener noreferrer">
null-ls.nvim
</a>
</h2>
<h3>
Use Neovim as a language server to inject LSP diagnostics, code actions, and more via Lua.
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<div class="markdown-heading">
<h1 class="heading-element">ARCHIVED</h1>
</div>
<p>null-ls is now archived and will no longer receive updates. Please see
<a href="https://github.com/jose-elias-alvarez/null-ls.nvim/issues/1621" rel="noopener noreferrer">this issue</a> for
details.</p>
<div class="markdown-heading">
<h1 class="heading-element">null-ls.nvim</h1>
</div>
<p>Use Neovim as a language server to inject LSP diagnostics, code actions, and
more via Lua.</p>
<div class="markdown-heading">
<h2 class="heading-element">Motivation</h2>
</div>
<p>Neovim's LSP ecosystem is growing, and plugins like
<a href="https://github.com/nvim-telescope/telescope.nvim" rel="noopener noreferrer">telescope.nvim</a> and
<a href="https://github.com/folke/trouble.nvim" rel="noopener noreferrer">trouble.nvim</a> make it a joy to work with
LSP features like code actions and diagnostics.</p>
<p>Unlike the VS Code and coc.nvim ecosystems, Neovim doesn't provide a way for
non-LSP sources to hook into its LSP client. null-ls is an attempt to bridge
that gap and simplify the process of creating, sharing, and setting up LSP
sources using pure Lua.</p>
<p>null-ls is also an attempt to reduce the boilerplate required to set up
general-purpose language servers and improve performance by removing the need
for external processes.</p>
<div class="markdown-heading">
<h2 class="heading-element">Status</h2>
</div>
<p>null-ls is in <strong>beta status</strong>. Please see below for steps to follow if something
doesn't work the way…</p>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/jose-elias-alvarez/null-ls.nvim" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>Gere diagnósticos, formate, realize ações de código e mais, utilizando esse plugin que utiliza o neovim como um servidor LSP, e faz a ponte entre ferramentas que não fazem uma conexão direta com o cliente nativo LSP. Entre os exemplos podemos citar:</p>
<ul>
<li>Formatar código ao salvar o buffer com o prettier</li>
<li>Gerar relatório de erros e warnings com golangci-lint para Go</li>
<li>Formatar código Lua com stylua</li>
</ul>
<p>e <a href="https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTINS.md" rel="noopener noreferrer">mais</a>! </p>
<h2>
#5
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/nvim-treesitter" rel="noopener noreferrer">
nvim-treesitter
</a> / <a href="https://github.com/nvim-treesitter/nvim-treesitter" rel="noopener noreferrer">
nvim-treesitter
</a>
</h2>
<h3>
Nvim Treesitter configurations and abstraction layer
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<div>
<div class="markdown-heading">
<h1 class="heading-element">nvim-treesitter</h1>
</div>
<p>
<a href="https://matrix.to/#/#nvim-treesitter:matrix.org" rel="nofollow noopener noreferrer">
<img alt="Matrix Chat" src="https://camo.githubusercontent.com/954e3fc1a9172d1c08e4e307b5361d27a5a48d45818951fffab09bdc86fa13e9/68747470733a2f2f696d672e736869656c64732e696f2f6d61747269782f6e76696d2d747265657369747465723a6d61747269782e6f7267" />
</a>
<a href="https://github.com/nvim-treesitter/nvim-treesitter/actions?query=workflow%3A%22Linting+and+style+checking%22+branch%3Amaster" rel="noopener noreferrer">
<img alt="Linting and Style" src="https://github.com/nvim-treesitter/nvim-treesitter/workflows/Linting%20and%20style%20checking/badge.svg" />
</a>
<a href="https://github.com/nvim-treesitter/nvim-treesitter/actions?query=workflow%3A%22Check+loading+of+syntax+files%22+branch%3Amaster" rel="noopener noreferrer">
<img alt="Syntax files" src="https://github.com/nvim-treesitter/nvim-treesitter/workflows/Check%20loading%20of%20syntax%20files/badge.svg" />
</a>
</p>
</div>
<div>
<p>
<a rel="noopener noreferrer" href="https://github.com/nvim-treesitter/nvim-treesitterassets/logo.png"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fnvim-treesitter%2Fnvim-treesitterassets%2Flogo.png" alt="Logo" /></a>
</p>
<p>
<a href="https://github.com/tree-sitter/tree-sitter" rel="noopener noreferrer">Treesitter</a>
configurations and abstraction layer for
<a href="https://github.com/neovim/neovim/" rel="noopener noreferrer">Neovim</a>
</p>
<p>
<i>
Logo by <a href="https://github.com/steelsojka" rel="noopener noreferrer">@steelsojka</a>
</i>
</p>
</div>
<p>The goal of <code>nvim-treesitter</code> is both to provide a simple and easy way to use the interface for <a href="https://github.com/tree-sitter/tree-sitter" rel="noopener noreferrer">tree-sitter</a> in Neovim and to provide some basic functionality such as highlighting based on it:</p>
<p><a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/2361214/202753610-e923bf4e-e88f-494b-bb1e-d22a7688446f.png"><img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F2361214%2F202753610-e923bf4e-e88f-494b-bb1e-d22a7688446f.png" alt="example-cpp" /></a></p>
<p>Traditional highlighting (left) vs Treesitter-based highlighting (right)
More examples can be found in <a href="https://github.com/nvim-treesitter/nvim-treesitter/wiki/Gallery" rel="noopener noreferrer">our gallery</a>.</p>
<p><strong>Warning: Treesitter and nvim-treesitter highlighting are an experimental feature of Neovim.
Please consider the experience with this plug-in as experimental until Tree-Sitter support in Neovim is stable!
We recommend using the nightly builds of Neovim if possible.
You can find the current roadmap <a href="https://github.com/nvim-treesitter/nvim-treesitter/issues/4767" rel="noopener noreferrer">here</a>.
The roadmap and all features of this plugin are open to change, and any suggestion will be highly appreciated!</strong></p>
<p>Nvim-treesitter is based on three interlocking features: <a href="https://github.com/nvim-treesitter/nvim-treesitter#language-parsers" rel="noopener noreferrer"><strong>language parsers</strong></a>, <a href="https://github.com/nvim-treesitter/nvim-treesitter#adding-queries" rel="noopener noreferrer"><strong>queries</strong></a>, and <a href="https://github.com/nvim-treesitter/nvim-treesitter#available-modules" rel="noopener noreferrer"><strong>modules</strong></a>, where <em>modules</em> provide features – e.g., highlighting – based on…</p>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/nvim-treesitter/nvim-treesitter" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>Um plugin que faz interface com o excelente projeto <a href="https://github.com/tree-sitter/tree-sitter" rel="noopener noreferrer">tree-sitter</a> e traz funcionalides como _syntax highlighting _, indentação, seleção incremental, movimentação, folding, entre outros. </p>
<h2>
#4
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/L3MON4D3" rel="noopener noreferrer">
L3MON4D3
</a> / <a href="https://github.com/L3MON4D3/LuaSnip" rel="noopener noreferrer">
LuaSnip
</a>
</h2>
<h3>
Snippet Engine for Neovim written in Lua.
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<p><a href="https://matrix.to/#/%23luasnip:matrix.org" rel="nofollow noopener noreferrer"><img src="https://camo.githubusercontent.com/7e4c0c2d22ff5f4cb470267e74a6e420bc201b085070d0ff43c6626e84dfb09a/68747470733a2f2f696d672e736869656c64732e696f2f6d61747269782f6c7561736e69703a6d61747269782e6f72673f6c6162656c3d4d6174726978266c6f676f3d6d6174726978" alt="LuaSnip" /></a></p>
<div class="markdown-heading">
<h1 class="heading-element">LuaSnip</h1>
</div>
<span class="m-1">javadoc.mp4</span>
<div class="markdown-heading">
<h1 class="heading-element">Features</h1>
</div>
<ul>
<li>Tabstops</li>
<li>Text-Transformations using Lua functions</li>
<li>Conditional Expansion</li>
<li>Defining nested Snippets</li>
<li>Filetype-specific Snippets</li>
<li>Choices</li>
<li>Dynamic Snippet creation</li>
<li>Regex-Trigger</li>
<li>Autotriggered Snippets</li>
<li>Easy Postfix Snippets</li>
<li>Fast</li>
<li>Parse <a href="https://microsoft.github.io/language-server-protocol/specification#snippet_syntax" rel="nofollow noopener noreferrer">LSP-Style</a> Snippets either directly in Lua, as a VSCode package or a SnipMate snippet collection.</li>
<li>Expand LSP-Snippets with <a href="https://github.com/hrsh7th/nvim-compe" rel="noopener noreferrer">nvim-compe</a> (or its' successor, <a href="https://github.com/hrsh7th/nvim-cmp" rel="noopener noreferrer">nvim-cmp</a> (requires <a href="https://github.com/saadparwaiz1/cmp_luasnip" rel="noopener noreferrer">cmp_luasnip</a>))</li>
<li>Snippet history (jump back into older snippets)</li>
<li>Resolve filetype at the cursor using Treesitter</li>
</ul>
<div class="markdown-heading">
<h1 class="heading-element">Drawbacks</h1>
</div>
<ul>
<li>Snippets that make use of the entire functionality of this plugin have to be defined in Lua (but 95% of snippets can be written in LSP-syntax).</li>
</ul>
<div class="markdown-heading">
<h1 class="heading-element">Requirements</h1>
</div>
<p>Neovim >= 0.7 (extmarks)
<code>jsregexp</code> for <code>lsp-snippet-transformations</code> (see <a href="https://github.com/L3MON4D3/LuaSnip/blob/master/DOC.md#transformations" rel="noopener noreferrer">here</a> for some tips on installing it).</p>
<div class="markdown-heading">
<h1 class="heading-element">Setup</h1>
</div>
<div class="markdown-heading">
<h2 class="heading-element">Install</h2>
</div>
<ul>
<li>
<p>With your preferred plugin manager i.e. <a href="https://github.com/junegunn/vim-plug" rel="noopener noreferrer">vim-plug</a>, <a href="https://github.com/wbthomason/packer.nvim" rel="noopener noreferrer">Packer</a> or <a href="https://github.com/folke/lazy.nvim" rel="noopener noreferrer">lazy</a><br />
<strong>Packer</strong>:</p>
<div class="highlight highlight-source-lua notranslate position-relative overflow-auto js-code-highlight">
<pre><span class="pl-c1">use</span>({
<span class="pl-s"><span class="pl-pds">"</span>L3MON4D3/LuaSnip<span class="pl-pds">"</span></span>
<span class="pl-c"><span class="pl-c">--</span> follow latest release.</span>
<span class="pl-smi">tag</span> <span class="pl-k">=</span> <span class="pl-s"><span class="pl-pds">"</span>v2.*<span class="pl-pds">"</span></span>, <span class="pl-c"><span class="pl-c">--</span> Replace <CurrentMajor> by the latest released major (first</span></pre>…
</div>
</li>
</ul>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/L3MON4D3/LuaSnip" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>O melhor script para criação de snippets do Neovim. Simples assim. Tem uma curva de aprendizado um pouco alta, mas quando se pega o jeito, você não vai querer largar nunca. <a href="https://www.youtube.com/watch?v=Dn800rlPIho" rel="noopener noreferrer">Aqui</a> um vídeo explicando mais sobre as funcionalidades dele (Em inglês)</p>
<h2>
#3
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/hrsh7th" rel="noopener noreferrer">
hrsh7th
</a> / <a href="https://github.com/hrsh7th/nvim-cmp" rel="noopener noreferrer">
nvim-cmp
</a>
</h2>
<h3>
A completion plugin for neovim coded in Lua.
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<div class="markdown-heading">
<h1 class="heading-element">nvim-cmp</h1>
</div>
<p>A completion engine plugin for neovim written in Lua
Completion sources are installed from external repositories and "sourced".</p>
<span class="m-1">demo.mp4</span>
<div class="markdown-heading">
<h1 class="heading-element">Readme!</h1>
</div>
<ol>
<li>There is a GitHub issue that documents <a href="https://github.com/hrsh7th/nvim-cmp/issues/231" rel="noopener noreferrer">breaking changes</a> for nvim-cmp. Subscribe to the issue to be notified of upcoming breaking changes.</li>
<li>This is my hobby project. You can support me via GitHub sponsors.</li>
<li>Bug reports are welcome, but don't expect a fix unless you provide minimal configuration and steps to reproduce your issue.</li>
<li>The <code>cmp.mapping.preset.*</code> is pre-defined configuration that aims to mimic neovim's native like behavior. It can be changed without announcement. Please manage key-mapping by yourself.</li>
</ol>
<div class="markdown-heading">
<h1 class="heading-element">Concept</h1>
</div>
<ul>
<li>Full support for LSP completion related capabilities</li>
<li>Powerful customizability via Lua functions</li>
<li>Smart handling of key mappings</li>
<li>No flicker</li>
</ul>
<div class="markdown-heading">
<h1 class="heading-element">Setup</h1>
</div>
<div class="markdown-heading">
<h3 class="heading-element">Recommended Configuration</h3>
</div>
<p>This example configuration uses <code>vim-plug</code> as the plugin manager and <code>vim-vsnip</code> as a snippet plugin.</p>
<div class="highlight highlight-source-viml notranslate position-relative overflow-auto js-code-highlight">
<pre><span class="pl-c1">call</span> <span class="pl-en">plug#begin</span>(<span class="pl-smi"><span class="pl-k">s:</span>plug_dir</span>)
Plug <span class="pl-s"><span class="pl-pds">'</span>neovim/nvim-lspconfig<span class="pl-pds">'</span></span>
Plug <span class="pl-s"><span class="pl-pds">'</span>hrsh7th/cmp-nvim-lsp</span></pre>…
</div>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/hrsh7th/nvim-cmp" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>Seguindo o melhor plugin para snippets, esse com certeza vai ser o melhor plugin para autocomplete que você irá ter no mercado até o momento. Seu design flexível permite incluir vários tipos diferentes de <em>sources</em>, desde sugestões de LSP, snippets, etc. A lista é <a href="https://github.com/hrsh7th/nvim-cmp/wiki/List-of-sources" rel="noopener noreferrer">gigantesca</a> e você também conta com uma excelente documentação caso queira criar os seus próprios sources também.</p>
<h2>
#2
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/nvim-lualine" rel="noopener noreferrer">
nvim-lualine
</a> / <a href="https://github.com/nvim-lualine/lualine.nvim" rel="noopener noreferrer">
lualine.nvim
</a>
</h2>
<h3>
A blazing fast and easy to configure neovim statusline plugin written in pure lua.
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<div class="markdown-heading">
<h1 class="heading-element">lualine.nvim</h1>
</div>
<p><a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/6da4d97c7bbc5bccb5827dacee2c9ab833fc2f7d7e800a518b68e5304c0937dd/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c616e6775616765732f636f64652d73697a652f6e76696d2d6c75616c696e652f6c75616c696e652e6e76696d3f7374796c653d666c61742d737175617265"><img src="https://camo.githubusercontent.com/6da4d97c7bbc5bccb5827dacee2c9ab833fc2f7d7e800a518b68e5304c0937dd/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c616e6775616765732f636f64652d73697a652f6e76696d2d6c75616c696e652f6c75616c696e652e6e76696d3f7374796c653d666c61742d737175617265" alt="code size" /></a>
<a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/626a72ea58ecf1972a27235993041be5014e1ef417a65b4988a1ef0a13457219/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6e76696d2d6c75616c696e652f6c75616c696e652e6e76696d3f7374796c653d666c61742d737175617265"><img src="https://camo.githubusercontent.com/626a72ea58ecf1972a27235993041be5014e1ef417a65b4988a1ef0a13457219/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f6e76696d2d6c75616c696e652f6c75616c696e652e6e76696d3f7374796c653d666c61742d737175617265" alt="license" /></a>
<a href="https://buymeacoffee.com/shadmansalj" rel="nofollow noopener noreferrer"><img src="https://camo.githubusercontent.com/ad541f86521726ee2afc61b260dcb24d3a359f0bdd4f8f7ddff53456eba73b64/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4275792532304d6525323061253230436f666665652d4637393431443f7374796c653d706c6173746963266c6f676f3d6275792d6d652d612d636f66666565266c6f676f436f6c6f723d7768697465" alt="Buy Me a Coffee" /></a></p>
<p>A blazing fast and easy to configure Neovim statusline written in Lua.</p>
<p><code>lualine.nvim</code> requires Neovim >= 0.7.</p>
<p>For previous versions of neovim please use compatability tags for example
compat-nvim-0.5</p>
<div class="markdown-heading">
<h2 class="heading-element">Contributing</h2>
</div>
<p>Feel free to create an issue/PR if you want to see anything else implemented.
If you have some question or need help with configuration, start a <a href="https://github.com/nvim-lualine/lualine.nvim/discussions" rel="noopener noreferrer">discussion</a>.</p>
<p>Please read <a href="https://github.com/nvim-lualine/lualine.nvim./CONTRIBUTING.md" rel="noopener noreferrer">CONTRIBUTING.md</a> before opening a PR.
You can also help with documentation in the <a href="https://github.com/nvim-lualine/lualine.nvim/wiki" rel="noopener noreferrer">wiki</a>.</p>
<div class="markdown-heading">
<h2 class="heading-element">Screenshots</h2>
</div>
<p>Here is a preview of what lualine can look like.</p>
<p>
<a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/41551030/108650373-bb025580-74bf-11eb-8682-2c09321dd18e.png"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F41551030%2F108650373-bb025580-74bf-11eb-8682-2c09321dd18e.png" /></a>
<a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/41551030/108650377-bd64af80-74bf-11eb-9c55-fbfc51b39fe8.png"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F41551030%2F108650377-bd64af80-74bf-11eb-9c55-fbfc51b39fe8.png" /></a>
<a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/41551030/108650378-be95dc80-74bf-11eb-9718-82b242ecdd54.png"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F41551030%2F108650378-be95dc80-74bf-11eb-9718-82b242ecdd54.png" /></a>
<a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/41551030/108650381-bfc70980-74bf-11eb-9245-85c48f0f154a.png"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F41551030%2F108650381-bfc70980-74bf-11eb-9245-85c48f0f154a.png" /></a>
<a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/41551030/103467925-32372b00-4d54-11eb-88d6-6d39c46854d8.png"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F41551030%2F103467925-32372b00-4d54-11eb-88d6-6d39c46854d8.png" /></a>
</p>
<p>Screenshots of all available themes are listed in <a href="https://github.com/nvim-lualine/lualine.nvim./THEMES.md" rel="noopener noreferrer">THEMES.md</a></p>
<p>For those who want to break the norms, you can create custom looks for lualine.</p>
<p><strong>Example</strong> :</p>
<ul>
<li>
<a href="https://github.com/nvim-lualine/lualine.nvimexamples/evil_lualine.lua" rel="noopener noreferrer">evil_lualine</a>
<a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/13149513/113875129-4453ba00-97d8-11eb-8f21-94a9ef565db3.png"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F13149513%2F113875129-4453ba00-97d8-11eb-8f21-94a9ef565db3.png" /></a>
</li>
<li>
<a href="https://github.com/nvim-lualine/lualine.nvimexamples/slanted-gaps.lua" rel="noopener noreferrer">slanted-gaps</a>
<a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/13149513/143395518-f6d6f748-c1ca-491b-9dab-246d0a8cf23f.png"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F13149513%2F143395518-f6d6f748-c1ca-491b-9dab-246d0a8cf23f.png" /></a>
</li>
<li>
<a href="https://github.com/nvim-lualine/lualine.nvimexamples/bubbles.lua" rel="noopener noreferrer">bubbles</a>
<a rel="noopener noreferrer nofollow" href="https://user-images.githubusercontent.com/20235646/131350468-fc556196-5f46-4bfe-a72e-960f6a58db2c.png"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fuser-images.githubusercontent.com%2F20235646%2F131350468-fc556196-5f46-4bfe-a72e-960f6a58db2c.png" /></a>
</li>
<li>
<a href="https://github.com/nvim-lualine/lualine.nvimexamples/cosmicink.lua" rel="noopener noreferrer">cosmicink</a>
<a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/125453388/423959036-c8d3e4ba-4997-42e9-a1bb-d5e2a444bbfd.png?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDQ4MTIxMTAsIm5iZiI6MTc0NDgxMTgxMCwicGF0aCI6Ii8xMjU0NTMzODgvNDIzOTU5MDM2LWM4ZDNlNGJhLTQ5OTctNDJlOS1hMWJiLWQ1ZTJhNDQ0YmJmZC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwNDE2JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDQxNlQxMzU2NTBaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0wOWU3Nzg3M2NhNzQ4YzY1ZjVhNTVmNDI2MDNkMTM3ODY5MzNiZjQ2NzYyMWE0MmFjYjk3ODkwYmI3ZmEzZWM0JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.nAz4h2CN1evpZdG8RPdAhGxdROfh57jmLX71a34_ZFE"><img width="700" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F125453388%2F423959036-c8d3e4ba-4997-42e9-a1bb-d5e2a444bbfd.png%3Fjwt%3DeyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NDQ4MTIxMTAsIm5iZiI6MTc0NDgxMTgxMCwicGF0aCI6Ii8xMjU0NTMzODgvNDIzOTU5MDM2LWM4ZDNlNGJhLTQ5OTctNDJlOS1hMWJiLWQ1ZTJhNDQ0YmJmZC5wbmc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBVkNPRFlMU0E1M1BRSzRaQSUyRjIwMjUwNDE2JTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDI1MDQxNlQxMzU2NTBaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT0wOWU3Nzg3M2NhNzQ4YzY1ZjVhNTVmNDI2MDNkMTM3ODY5MzNiZjQ2NzYyMWE0MmFjYjk3ODkwYmI3ZmEzZWM0JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCJ9.nAz4h2CN1evpZdG8RPdAhGxdROfh57jmLX71a34_ZFE" /></a>
</li>
</ul>
<div class="markdown-heading">
<h2 class="heading-element">Performance compared to other plugins</h2>
</div>
<p>Unlike other statusline plugins, lualine loads only the components you specify, and nothing else.</p>
<p>Startup time performance measured with an amazing plugin <a href="https://github.com/dstein64/vim-startuptime" rel="noopener noreferrer">dstein64/vim-startuptime</a></p>
<p>Times are measured with a…</p>
</div>
</div>
<br />
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/nvim-lualine/lualine.nvim" rel="noopener noreferrer">View on GitHub</a></div>
<br />
</div>
<br />
<p>Minha escolha para plugin de statusline. Simples, rápido e bem flexível.</p>
<h2>
#1
</h2>
<div class="ltag-github-readme-tag">
<div class="readme-overview">
<h2>
<img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo" />
<a href="https://github.com/nvim-telescope" rel="noopener noreferrer">
nvim-telescope
</a> / <a href="https://github.com/nvim-telescope/telescope.nvim" rel="noopener noreferrer">
telescope.nvim
</a>
</h2>
<h3>
Find, Filter, Preview, Pick. All lua, all the time.
</h3>
</div>
<div class="ltag-github-body">
<div id="readme" class="md">
<div class="markdown-heading">
<h1 class="heading-element">telescope.nvim</h1>
</div>
<p><a href="https://gitter.im/nvim-telescope/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge" rel="nofollow noopener noreferrer"><img src="https://camo.githubusercontent.com/4768045434e6ee57bdd36b11fcf554e88d207acd01e0b78a2ef3c04b2f8a7d25/68747470733a2f2f6261646765732e6769747465722e696d2f6e76696d2d74656c6573636f70652f636f6d6d756e6974792e737667" alt="Gitter" /></a>
<a href="https://luarocks.org/modules/Conni2461/telescope.nvim" rel="nofollow noopener noreferrer"><img src="https://camo.githubusercontent.com/4b597076c327f7a8f7005e116cdd17c5e936a7d2a8f99655b31f1c81468b6fc4/68747470733a2f2f696d672e736869656c64732e696f2f6c7561726f636b732f762f436f6e6e69323436312f74656c6573636f70652e6e76696d3f6c6f676f3d6c756126636f6c6f723d707572706c65" alt="LuaRocks" /></a></p>
<p>Gaze deeply into unknown regions using the power of the moon.</p>
<div class="markdown-heading">
<h2 class="heading-element">What Is Telescope?</h2>
</div>
<p><code>telescope.nvim</code> is a highly extendable fuzzy finder over lists. Built on the
latest awesome features from <code>neovim</code> core. Telescope is centered around
modularity, allowing for easy customization.</p>
<p>Community driven builtin <a href="https://github.com/nvim-telescope/telescope.nvim#pickers" rel="noopener noreferrer">pickers</a>, <a href="https://github.com/nvim-telescope/telescope.nvim#sorters" rel="noopener noreferrer">sorters</a> and
<a href="https://github.com/nvim-telescope/telescope.nvim#previewers" rel="noopener noreferrer">previewers</a>.</p>
<p><a rel="noopener noreferrer nofollow" href="https://camo.githubusercontent.com/5eb234defa4dcc0145ba0d8e327a03869f83af8ac0def9007828e4835dfecd32/68747470733a2f2f692e696d6775722e636f6d2f5454546a6136742e676966"><img src="https://camo.githubusercontent.com/5eb234defa4dcc0145ba0d8e327a03869f83af8ac0def9007828e4835dfecd32/68747470733a2f2f692e696d6775722e636f6d2f5454546a6136742e676966" alt="Preview" /></a>
For more showcases of Telescope, please visit the <a href="https://github.com/nvim-telescope/telescope.nvim/wiki/Showcase" rel="noopener noreferrer">Showcase
section</a> in the
Telescope Wiki</p>
<div class="markdown-heading">
<h2 class="heading-element">Telescope Table of Contents</h2>
</div>
<ul>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#getting-started" rel="noopener noreferrer">Getting Started</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#usage" rel="noopener noreferrer">Usage</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#customization" rel="noopener noreferrer">Customization</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#default-mappings" rel="noopener noreferrer">Default Mappings</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#pickers" rel="noopener noreferrer">Pickers</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#previewers" rel="noopener noreferrer">Previewers</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#sorters" rel="noopener noreferrer">Sorters</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#layout-display" rel="noopener noreferrer">Layout</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#themes" rel="noopener noreferrer">Themes</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#vim-commands" rel="noopener noreferrer">Commands</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#autocmds" rel="noopener noreferrer">Autocmds</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#extensions" rel="noopener noreferrer">Extensions</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#api" rel="noopener noreferrer">API</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#media" rel="noopener noreferrer">Media</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim#contributing" rel="noopener noreferrer">Contributing</a></li>
<li><a href="https://github.com/nvim-telescope/telescope.nvim/blob/master/doc/telescope_changelog.txt" rel="noopener noreferrer">Changelog</a></li>
</ul>
<div class="markdown-heading">
<h2 class="heading-element">Getting Started</h2>
</div>
<p>This section should guide you to run your first builtin pickers.</p>
<p><a href="https://github.com/neovim/neovim/releases/tag/v0.9.0" rel="noopener noreferrer">Neovim (v0.9.0)</a> or the
latest neovim nightly commit is required for <code>telescope.nvim</code> to work.
The neovim version also needs to be compiled with LuaJIT, we currently do not
support Lua5.1 because of some ongoing issues.</p>
<div class="markdown-heading">
<h3 class="heading-element">Required dependencies</h3>
</div>
<ul>
<li>
<a href="https://github.com/nvim-lua/plenary.nvim" rel="noopener noreferrer">nvim-lua/plenary.nvim</a> is required.</li>
</ul>
<div class="markdown-heading">
<h3 class="heading-element">Suggested dependencies</h3>
</div>
<ul>
<li>
<a href="https://github.com/BurntSushi/ripgrep" rel="noopener noreferrer">BurntSushi/ripgrep</a> is required for
<code>live_grep</code> and <code>grep_string</code> and…</li>
</ul>
</div>
</div>
<div class="gh-btn-container"><a class="gh-btn" href="https://github.com/nvim-telescope/telescope.nvim" rel="noopener noreferrer">View on GitHub</a></div>
</div>
<p>Talvez o plugin mais essencial de todos hoje. Faça buscas por arquivo, texto e muito mais, numa velocidade absurda, com esse plugin fantástico.</p>
<h2>
Conclusão
</h2>
<p>Com o ecossistema Lua se desenvolvendo cada dia mais, <a href="https://github.com/topics/neovim-plugin?l=lua" rel="noopener noreferrer">muitos plugins estão surgindo todos os dias</a> e com certeza essa lista provavelmente precisará ser atualizada em um futuro próximo.</p>
<p><strong>Faltou algum plugin nessa lista? Me conta ae nos comentários!</strong></p>
2022-05-13T17:56:41+00:00
-
PythonClub: Participe da Python Brasil 2021, a maior conferência de Python da América Latina
https://pythonclub.com.br/python-brasil-2021.html
<p><em>Python Brasil 2021, a maior conferência de Python da América Latina, acontecerá entre os dias 11 e 17 de outubro, reunindo pessoas desenvolvedoras, cientistas de dados e entusiastas da tecnologia.</em></p>
<p><img alt="alt text" src="https://pythonclub.com.br/images/rafpyprog/pybr-banner.jpeg" title="Python Brasil 2021" /></p>
<p>A <a href="https://2021.pythonbrasil.org.br/">Python Brasil 2021</a>, evento promovido pela comunidade brasileira de Python, traz nesta edição uma agenda imperdível para quem quer mergulhar no universo de uma das linguagens de programação mais utilizadas na atualidade.</p>
<p>Uma das principais novidades deste ano é a trilha de atividades em espanhol, que vai trazer ao evento palestras de toda a América Latina, criando um ambiente de muita diversidade onde as comunidades Python de língua espanhola vão poder se conectar e trocar experiências.</p>
<p>Em sua 17 edição, o evento será realizado de forma online, gratuito e aberto para qualquer pessoa de 11 a 17 de outubro. É esperado um público de mais de 10 mil participantes de todas as partes do Brasil e do mundo.
Nos sete dias de imersão, os participantes poderão contribuir para projetos de software livre, participar de treinamentos e adquirir novos conhecimentos com outras pessoas da comunidade.</p>
<p>E que tal falar sobre aquele projeto inovador, dar dicas sobre carreira ou mostrar formas de usar a tecnologia para causar impacto social, tudo isso em um ambiente inclusivo, seguro e acolhedor?! Até o dia 12 de agosto você poderá <a href="https://docs.google.com/forms/d/e/1FAIpQLSfXA7KGJbmoE6BHRgWAtK8LlBTEULv8QTS8ffHLIKUgeiLkZA/viewform">submeter sua proposta</a> para apresentação de palestras e tutoriais na maior conferência Python da América Latina.</p>
<p>Você poderá acompanhar a Python Brasil pelo YouTube em tempo real e também participar de atividades e interagir com toda a comunidade pelo servidor do Discord criado especialmente para o evento.</p>
<p>Não deixe de seguir o <a href="https://instagram.com/pythonbrasil?utm_medium=copy_link">perfil do evento no Instagram</a> ou <a href="https://twitter.com/pythonbrasil">Twitter</a> para ficar por dentro das últimas novidades!</p>
2021-08-02T18:00:00+00:00
Rafael Alves RIbeiro
-
Filipe Saraiva: Ciclo de Entrevistas sobre as Pesquisas no PPGCC da UFPA – Inteligência Computacional
https://blog.filipesaraiva.info/?p=2250
<p><img /></p>
<p>A <a href="https://computacao.ufpa.br/">Faculdade de Computação</a> e o <a href="https://ppgcc.propesp.ufpa.br/">Programa de Pós-Graduação em Ciência da Computação</a> da <a href="https://ufpa.br/">UFPA</a> estão desenvolvendo um projeto que pretende atingir dois objetivos: o primeiro, fazer uma melhor divulgação para o público externo à universidade do que produzimos em nossas pesquisas; o segundo, uma melhor divulgação INTERNA da mesma coisa – o que desenvolvemos em nossas pesquisas.</p>
<p>Sim, <strong>INTERNA</strong> – não bastasse de fato a comunicação deficitária e pulverizada do que fazemos para os nossos próprios alunos, a pandemia só veio a piorar esse quadro. Após mais de um ano sem contato próximo com as turmas, com aulas completamente à distância e sem maiores interações extra-classe, os alunos em geral estão perdidos sobre as possibilidades de temas para TCCs e pesquisas que eles podem realizar conosco.</p>
<p>Dessa forma as duas subunidades estão entrevistando todos os professores para que falem um pouco sobre os temas que trabalham, o histórico de pesquisa, o que vem sendo feito e, mais interessante, o que pode vir a ser feito. As entrevistas ocorrem no canal do YouTube <a href="https://www.youtube.com/c/ComputaçãoUFPA/">Computação UFPA</a> e depois são retrabalhadas para aparecerem no <a href="https://open.spotify.com/show/7oGkFKS4eV4WM2XjbEFeJu">FacompCast</a>.</p>
<p>Feitas as devidas introduções, nesta terça dia 22/06 às 11h eu e o amigo prof. Jefferson Morais iremos falar um pouco sobre as pesquisas em Inteligência Computacional (ou seria Artificial?) desenvolvidas por nós. Será um bom apanhado sobre os trabalhos em 4 áreas que atuamos – aprendizado de máquina, metaheurísticas, sistemas fuzzy e sistemas multiagentes -, expondo projetos atuais e novos para os interessados.</p>
<p>Nos vemos portanto logo mais na <a href="https://www.youtube.com/watch?v=VRytpiFWTQI">sala da entrevista</a>.</p>
<p><strong>UPDATE:</strong></p>
<p>A gravação já está disponível, segue abaixo:</p>
<p></p>
2021-06-21T21:51:57+00:00
-
PythonClub: Orientação a objetos de outra forma: Property
https://pythonclub.com.br/oo-de-outra-forma-6.html
<p>Seguindo com a série, chegou a hora de discutir sobre encapsulamento, ou seja, ocultar detalhes de implementação de uma classe do resto do código. Em algumas linguagens de programação isso é feito utilizando <code>protected</code> ou <code>private</code>, e às vezes o acesso aos atributos é feito através de funções <em>getters</em> e <em>setters</em>. Nesse texto vamos ver como o Python lida com essas questões.</p>
<h2>Métodos protegidos e privados</h2>
<p>Diferente de linguagens como Java e PHP que possuem palavras-chave como <code>protected</code> e <code>private</code> para impedir que outras classes acessem determinados métodos ou atributos, Python deixa tudo como público. Porém isso não significa que todas as funções de uma classe podem ser chamadas por outras, ou todos os atributos podem ser lidos e alterados sem cuidados.</p>
<p>Para que quem estiver escrevendo um código saiba quais as funções ou atributos que não deveriam ser acessados diretamente, segue-se o padrão de começá-los com <code>_</code>, de forma similar aos arquivos ocultos em sistemas UNIX, que começam com <code>.</code>. Esse padrão já foi seguido na classe <code>AutenticavelComRegistro</code> da postagem sobre <a href="https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-heranca-multiplas-e-mixins-31eb">mixins</a>, onde a função que pega a data do sistema foi nomeada <code>_get_data</code>. Entretanto isso é apenas uma sugestão, nada impede dela ser chamada, como no exemplo a baixo:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="k">class</span> <span class="nc">Exemplo</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">_get_data</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s1">'</span><span class="si">%d</span><span class="s1">/%m/%Y %T'</span><span class="p">)</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">Exemplo</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">obj</span><span class="o">.</span><span class="n">_get_data</span><span class="p">())</span>
</pre></div>
<p>Porém algumas bibliotecas também utilizam o <code>_</code> para indicar outras informações como metadados do objeto, e que podem ser acessados sem muitos problemas. Assim é possível utilizar esse símbolo duas vezes (<code>__</code>) para indicar que realmente essa variável ou função não deveria ser acessada de fora da classe, apresentando erro de que o atributo não foi encontrado ao tentar executar a função, porém ela ainda pode ser acessada:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="k">class</span> <span class="nc">Exemplo</span><span class="p">:</span>
<span class="k">def</span> <span class="nf">__get_data</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s1">'</span><span class="si">%d</span><span class="s1">/%m/%Y %T'</span><span class="p">)</span>
<span class="n">obj</span> <span class="o">=</span> <span class="n">Exemplo</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">obj</span><span class="o">.</span><span class="n">__get_data</span><span class="p">())</span> <span class="c1"># AttributeError</span>
<span class="nb">print</span><span class="p">(</span><span class="n">obj</span><span class="o">.</span><span class="n">_Exemplo__get_data</span><span class="p">())</span> <span class="c1"># Executa a função</span>
</pre></div>
<h2>Property</h2>
<p>Os <em>getters</em> e <em>setters</em> muitas vezes são usados para impedir que determinadas variáveis sejam alteradas, ou validar o valor antes de atribuir a variável, ou ainda processar um valor a partir de outras variáveis. Porém como o Python incentiva o acesso direto as variáveis, existe a <em>property</em>, que ao tentar acessar uma variável ou alterar um valor, uma função é chamada. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Pessoa</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_nome</span> <span class="o">=</span> <span class="n">nome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_idade</span> <span class="o">=</span> <span class="n">idade</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">nome</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_nome</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{self.nome}</span><span class="s1"> </span><span class="si">{self.sobrenome}</span><span class="s1">'</span>
<span class="nd">@nome_completo</span><span class="o">.</span><span class="n">setter</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="n">valor</span> <span class="o">=</span> <span class="n">valor</span><span class="o">.</span><span class="n">split</span><span class="p">(</span><span class="s1">' '</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_nome</span> <span class="o">=</span> <span class="n">valor</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">valor</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">idade</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_idade</span>
<span class="nd">@idade</span><span class="o">.</span><span class="n">setter</span>
<span class="k">def</span> <span class="nf">idade</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="k">if</span> <span class="n">valor</span> <span class="o"><</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">ValueError</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_idade</span> <span class="o">=</span> <span class="n">valor</span>
<span class="k">def</span> <span class="nf">fazer_aniversario</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">idade</span> <span class="o">+=</span> <span class="mi">1</span>
</pre></div>
<p>Nesse código algumas variáveis são acessíveis através de <em>properties</em>, de forma geral, as variáveis foram definidas começando com <code>_</code> e com uma <em>property</em> de mesmo nome (sem o <code>_</code>). O primeiro caso é o <code>nome</code>, que possui apenas o <em>getter</em>, sendo possível o seu acesso como <code>obj.nome</code>, porém ao tentar atribuir um valor, será lançado um erro (<code>AttributeError: can't set attribute</code>). Em relação ao <code>sobrenome</code>, como não é necessário nenhum tratamento especial, não foi utilizado um <em>property</em>, porém futuramente pode ser facilmente substituído por um sem precisar alterar os demais códigos. Porém a função <code>nome_completo</code> foi substituída por um <em>property</em>, permitindo tanto o acesso ao nome completo da pessoa, como se fosse uma variável, quanto trocar <code>nome</code> e <code>sobrenome</code> ao atribuir um novo valor para essa <em>property</em>. Quanto a <code>idade</code> utiliza o <em>setter</em> do <em>property</em> para validar o valor recebido, retornando erro para idades inválidas (negativas).</p>
<p>Vale observar também que todas as funções de <em>getter</em> não recebem nenhum argumento (além do <code>self</code>), enquanto as funções de <em>setter</em> recebem o valor atribuído à variável.</p>
<p>Utilizando a <a href="https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-abc-89b">ABC</a>, ainda é possível informar que alguma classe filha deverá implementar alguma <em>property</em>. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span>
<span class="k">class</span> <span class="nc">Pessoa</span><span class="p">(</span><span class="n">ABC</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nome</span> <span class="o">=</span> <span class="n">nome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">idade</span> <span class="o">=</span> <span class="n">idade</span>
<span class="nd">@property</span>
<span class="nd">@abstractmethod</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">class</span> <span class="nc">Brasileiro</span><span class="p">(</span><span class="n">Pessoa</span><span class="p">):</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{self.nome}</span><span class="s1"> </span><span class="si">{self.sobrenome}</span><span class="s1">'</span>
<span class="k">class</span> <span class="nc">Japones</span><span class="p">(</span><span class="n">Pessoa</span><span class="p">):</span>
<span class="nd">@property</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{self.sobrenome}</span><span class="s1"> </span><span class="si">{self.nome}</span><span class="s1">'</span>
</pre></div>
<h2>Considerações</h2>
<p>Diferente de algumas linguagens que ocultam as variáveis dos objetos, permitindo o seu acesso apenas através de funções, Python seguem no sentido contrário, acessando as funções de <em>getter</em> e <em>setter</em> como se fossem variáveis, isso permite começar com uma classe simples e ir adicionando funcionalidades conforme elas forem necessárias, sem precisar mudar o código das demais partes da aplicação, além de deixar transparente para quem desenvolve, não sendo necessário lembrar se precisa usar <em>getteres</em> e <em>setteres</em> ou não.</p>
<p>De forma geral, programação orientada a objetos consiste em seguir determinados padrões de código, e as linguagens que implementam esse paradigma oferecem facilidades para escrever código seguindo esses padrões, e às vezes até ocultando detalhes complexos de suas implementações. Nesse contexto, eu recomendo a palestra do autor do <a href="https://htop.dev/">htop</a> feita no FISL 16, onde ele comenta como usou <a href="https://hemingway.softwarelivre.org/fisl16/high/41f/sala_41f-high-201507091200.ogv">orientação a objetos em C</a>. E para quem ainda quiser se aprofundar no assunto de orientação a objetos no Python, recomendo os vídeos do <a href="https://www.youtube.com/playlist?list=PLOQgLBuj2-3L_L6ahsBVA_SzuGtKre3OK">Eduardo Mendes</a> (também conhecido como dunossauro).</p>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2021-05-17T21:00:00+00:00
Eduardo Klosowski
-
Blog do PauloHRPinheiro: Iniciando com o ORM Pony no Python III - Erros e Exceções
https://paulohrpinheiro.xyz/texts/python/2021-05-16-iniciando-com-o-orm-pony-no-python-iii-erros-e-excecoes.html
Seguindo o trilha com o ORM Pony, neste terceiro texto, agora começaremos a gerar situações de erro e ver o que os objetos retornam ou que exceções são geradas.
2021-05-16T00:00:00+00:00
-
PythonClub: Orientação a objetos de outra forma: ABC
https://pythonclub.com.br/oo-de-outra-forma-5.html
<p>Na discussão sobre <a href="https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-heranca-multiplas-e-mixins-31eb">herança e mixins</a> foram criadas várias classes, como <code>Autenticavel</code> e <code>AutenticavelComRegistro</code> que adicionam funcionalidades a outras classes e implementavam tudo o que precisavam para seu funcionamento. Entretanto podem existir casos em que não seja possível implementar todas as funções na própria classe, deixando com que as classes que a estende implemente essas funções. Uma forma de fazer isso é través das <a href="https://docs.python.org/pt-br/3/library/abc.html">ABC</a> (<em>abstract base classes</em>, ou classes base abstratas).</p>
<h2>Sem uso de classes base abstratas</h2>
<p>Um exemplo de classe que não é possível implementar todas as funcionalidades foi dada no texto <a href="https://dev.to/acaverna/encapsulamento-da-logica-do-algoritmo-298e">Encapsulamento da lógica do algoritmo</a>, que discutia a leitura de valores do teclado até que um valor válido fosse lido (ou que repete a leitura caso um valor inválido tivesse sido informado). Nesse caso a classe <code>ValidaInput</code> implementava a lógica base de funcionamento, porém eram suas classes filhas (<code>ValidaNomeInput</code> e <code>ValidaNotaInput</code>) que implementavam as funções para tratar o que foi lido do teclado e verificar se é um valor válido ou não.</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ValidaInput</span><span class="p">:</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Valor inválido!'</span>
<span class="k">def</span> <span class="nf">ler_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">input</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entrada</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
<span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">valor</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ler_entrada</span><span class="p">(</span><span class="n">prompt</span><span class="p">))</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validar_valor</span><span class="p">(</span><span class="n">valor</span><span class="p">):</span>
<span class="k">break</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="o">...</span>
<span class="nb">print</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">mensagem_valor_invalido</span><span class="p">)</span>
<span class="k">return</span> <span class="n">valor</span>
<span class="k">class</span> <span class="nc">ValidaNomeInput</span><span class="p">(</span><span class="n">ValidaInput</span><span class="p">):</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Nome inválido!'</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entrada</span><span class="p">):</span>
<span class="k">return</span> <span class="n">entrada</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">title</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="k">return</span> <span class="n">valor</span> <span class="o">!=</span> <span class="s1">''</span>
<span class="k">class</span> <span class="nc">ValidaNotaInput</span><span class="p">(</span><span class="n">ValidaInput</span><span class="p">):</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Nota inválida!'</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entrada</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="n">entrada</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="k">return</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">valor</span> <span class="o"><=</span> <span class="mi">10</span>
</pre></div>
<p>Entretanto, esse código permite a criação de objetos da classe <code>ValidaInput</code> mesmo sem ter uma implementação das funções <code>transformar_entrada</code> e <code>validar_valor</code>. E a única mensagem de erro ocorreria ao tentar executar essas funções, o que poderia estar longe do problema real, que é a criação de um objeto a partir de uma classe que não prove todas as implementações das suas funções, o que seria semelhante a uma classe abstrata em outras linguagens.</p>
<div class="highlight"><pre><span></span><span class="n">obj</span> <span class="o">=</span> <span class="n">ValidaInput</span><span class="p">()</span>
<span class="c1"># Diversas linhas de código</span>
<span class="n">obj</span><span class="p">(</span><span class="s1">'Entrada: '</span><span class="p">)</span> <span class="c1"># Exceção NotImplementedError lançada</span>
</pre></div>
<h2>Com uso de classes base abstratas</h2>
<p>Seguindo a documentação da <a href="https://docs.python.org/pt-br/3/library/abc.html">ABC</a>, para utilizá-las é necessário informar a metaclasse <code>ABCMeta</code> na criação da classe, ou simplesmente estender a classe <code>ABC</code>, e decorar com <code>abstractmethod</code> as funções que as classes que a estenderem deverão implementar. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
<span class="k">class</span> <span class="nc">ValidaInput</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Valor inválido!'</span>
<span class="k">def</span> <span class="nf">ler_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">input</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span>
<span class="nd">@abstractmethod</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entrada</span><span class="p">):</span>
<span class="o">...</span>
<span class="nd">@abstractmethod</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">valor</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ler_entrada</span><span class="p">(</span><span class="n">prompt</span><span class="p">))</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validar_valor</span><span class="p">(</span><span class="n">valor</span><span class="p">):</span>
<span class="k">break</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="o">...</span>
<span class="nb">print</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">mensagem_valor_invalido</span><span class="p">)</span>
<span class="k">return</span> <span class="n">valor</span>
</pre></div>
<p>Desta forma, ocorrerá um erro já ao tentar criar um objeto do tipo <code>ValidaInput</code>, dizendo quais são as funções que precisam ser implementadas. Porém funcionará normalmente ao criar objetos a partir das classes <code>ValidaNomeInput</code> e <code>ValidaNotaInput</code> visto que elas implementam essas funções.</p>
<div class="highlight"><pre><span></span><span class="n">obj</span> <span class="o">=</span> <span class="n">ValidaInput</span><span class="p">()</span> <span class="c1"># Exceção TypeError lançada</span>
<span class="n">nome_input</span> <span class="o">=</span> <span class="n">ValidaNomeInput</span><span class="p">()</span> <span class="c1"># Objeto criado</span>
<span class="n">nota_input</span> <span class="o">=</span> <span class="n">ValidaNotaInput</span><span class="p">()</span> <span class="c1"># Objeto criado</span>
</pre></div>
<p>Como essas funções não utilizam a referência ao objeto (<code>self</code>), ainda é possível decorar as funções com <code>staticmethod</code>, como:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
<span class="k">class</span> <span class="nc">ValidaInput</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Valor inválido!'</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">ler_entrada</span><span class="p">(</span><span class="n">prompt</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">input</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span>
<span class="nd">@staticmethod</span>
<span class="nd">@abstractmethod</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="n">entrada</span><span class="p">):</span>
<span class="o">...</span>
<span class="nd">@staticmethod</span>
<span class="nd">@abstractmethod</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="n">valor</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">valor</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ler_entrada</span><span class="p">(</span><span class="n">prompt</span><span class="p">))</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validar_valor</span><span class="p">(</span><span class="n">valor</span><span class="p">):</span>
<span class="k">break</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="o">...</span>
<span class="nb">print</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">mensagem_valor_invalido</span><span class="p">)</span>
<span class="k">return</span> <span class="n">valor</span>
<span class="k">class</span> <span class="nc">ValidaNomeInput</span><span class="p">(</span><span class="n">ValidaInput</span><span class="p">):</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Nome inválido!'</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="n">entrada</span><span class="p">):</span>
<span class="k">return</span> <span class="n">entrada</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">title</span><span class="p">()</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="n">valor</span><span class="p">):</span>
<span class="k">return</span> <span class="n">valor</span> <span class="o">!=</span> <span class="s1">''</span>
<span class="k">class</span> <span class="nc">ValidaNotaInput</span><span class="p">(</span><span class="n">ValidaInput</span><span class="p">):</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Nota inválida!'</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="n">entrada</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="n">entrada</span><span class="p">)</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="n">valor</span><span class="p">):</span>
<span class="k">return</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">valor</span> <span class="o"><=</span> <span class="mi">10</span>
</pre></div>
<p>Isso também seria válido para funções decoradas com <code>classmethod</code>, que receberiam a referência a classe (<code>cls</code>).</p>
<h2>Considerações</h2>
<p>Não é necessário utilizar ABC para fazer o exemplo discutido, porém ao utilizar essa biblioteca ficou mais explícito quais as funções que precisavam ser implementados nas classes filhas, ainda mais que sem utilizar ABC a classe base poderia nem ter as funções, com:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ValidaInput</span><span class="p">:</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Valor inválido!'</span>
<span class="k">def</span> <span class="nf">ler_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">input</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span>
<span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">valor</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ler_entrada</span><span class="p">(</span><span class="n">prompt</span><span class="p">))</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validar_valor</span><span class="p">(</span><span class="n">valor</span><span class="p">):</span>
<span class="k">break</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="o">...</span>
<span class="nb">print</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">mensagem_valor_invalido</span><span class="p">)</span>
<span class="k">return</span> <span class="n">valor</span>
</pre></div>
<p>Como Python possui <a href="https://docs.python.org/pt-br/3/glossary.html#term-duck-typing">duck-typing</a>, não é necessário uma grande preocupação com os tipos, como definir e utilizar interfaces presentes em outras implementações de orientação a objetos, porém devido à herança múltipla, ABC pode ser utilizada como interface que não existe em Python, fazendo com que as classes implementem determinadas funções. Para mais a respeito desse assunto, recomendo as duas lives do dunossauro sobre ABC (<a href="https://www.youtube.com/watch?v=yLHV1__nZZw">1</a> e <a href="https://www.youtube.com/watch?v=erAXvsuihPQ">2</a>), e a apresentação do Luciano Ramalho sobre <a href="https://www.youtube.com/watch?v=AJK2LqrlnTE">type hints</a>.</p>
<p>Uma classe filha também não é obrigada a implementar todas as funções decoradas com <code>abstractmethod</code>, mas assim como a classe pai, não será possível criar objetos a partir dessa classe, apenas de uma classe filha dela que implemente as demais funções. Como se ao aplicar um <code>abstractmethod</code> tornasse a classe abstrata, e qualquer classe filha só deixasse de ser abstrata quando a última função decorada com <code>abstractmethod</code> for sobrescrita. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">abc</span> <span class="kn">import</span> <span class="n">ABC</span><span class="p">,</span> <span class="n">abstractmethod</span>
<span class="k">class</span> <span class="nc">A</span><span class="p">(</span><span class="n">ABC</span><span class="p">):</span>
<span class="nd">@abstractmethod</span>
<span class="k">def</span> <span class="nf">func1</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="o">...</span>
<span class="nd">@abstractmethod</span>
<span class="k">def</span> <span class="nf">func2</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">class</span> <span class="nc">B</span><span class="p">(</span><span class="n">A</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">func1</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'1'</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">C</span><span class="p">(</span><span class="n">B</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">func2</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'2'</span><span class="p">)</span>
<span class="n">a</span> <span class="o">=</span> <span class="n">A</span><span class="p">()</span> <span class="c1"># Erro por não implementar func1 e func2</span>
<span class="n">b</span> <span class="o">=</span> <span class="n">B</span><span class="p">()</span> <span class="c1"># Erro por não implementar func2</span>
<span class="n">c</span> <span class="o">=</span> <span class="n">C</span><span class="p">()</span> <span class="c1"># Objeto criado</span>
</pre></div>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2021-05-10T15:00:00+00:00
Eduardo Klosowski
-
PythonClub: Orientação a objetos de outra forma: Herança múltiplas e mixins
https://pythonclub.com.br/oo-de-outra-forma-4.html
<p>No <a href="https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-heranca-3dm7">texto anterior</a> foi apresentando o conceito de herança, que herda toda a estrutura e comportamento de uma classe, podendo estendê-la com outros atributos e comportamentos. Esse texto apresentará a ideia de <a href="https://pt.wikipedia.org/wiki/Heran%C3%A7a_m%C3%BAltipla">herança múltipla</a>, e uma forma para se aproveitar esse recurso, através de mixins.</p>
<h2>Herança múltiplas</h2>
<p>Voltando ao sistema para lidar com dados das pessoas, onde algumas dessas pessoas possuem a possibilidade de acessar o sistema através de usuário e senha, também deseja-se permitir que outros sistemas autentiquem e tenham acesso os dados através de uma <a href="https://pt.wikipedia.org/wiki/Interface_de_programa%C3%A7%C3%A3o_de_aplica%C3%A7%C3%B5es">API</a>. Isso pode ser feito criando uma classe para representar os sistemas que terão permissão para acessar os dados. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Sistema</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">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">=</span> <span class="n">usuario</span>
<span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">=</span> <span class="n">senha</span>
<span class="k">def</span> <span class="nf">autenticar</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">==</span> <span class="n">usuario</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">==</span> <span class="n">senha</span>
</pre></div>
<p>Porém, esse código repete a implementação feita para <code>PessoaAutenticavel</code>:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">PessoaAutenticavel</span><span class="p">(</span><span class="n">Pessoa</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">=</span> <span class="n">usuario</span>
<span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">=</span> <span class="n">senha</span>
<span class="k">def</span> <span class="nf">autenticar</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">==</span> <span class="n">usuario</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">==</span> <span class="n">senha</span>
</pre></div>
<p>Aproveitando que Python, diferente de outras linguagens, possui herança múltipla, é possível extrair essa lógica das classes, centralizando a implementação em uma outra classe e simplesmente herdá-la. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Autenticavel</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="o">*</span><span class="n">args</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</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="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">=</span> <span class="n">usuario</span>
<span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">=</span> <span class="n">senha</span>
<span class="k">def</span> <span class="nf">autenticar</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">==</span> <span class="n">usuario</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">==</span> <span class="n">senha</span>
<span class="k">class</span> <span class="nc">PessoaAutenticavel</span><span class="p">(</span><span class="n">Autenticavel</span><span class="p">,</span> <span class="n">Pessoa</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">class</span> <span class="nc">Sistema</span><span class="p">(</span><span class="n">Autenticavel</span><span class="p">):</span>
<span class="o">...</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">PessoaAutenticavel</span><span class="p">(</span><span class="n">nome</span><span class="o">=</span><span class="s1">'João'</span><span class="p">,</span> <span class="n">sobrenome</span><span class="o">=</span><span class="s1">'da Silva'</span><span class="p">,</span> <span class="n">idade</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span>
<span class="n">usuario</span><span class="o">=</span><span class="s1">'joao'</span><span class="p">,</span> <span class="n">senha</span><span class="o">=</span><span class="s1">'secreta'</span><span class="p">)</span>
</pre></div>
<p>A primeira coisa a ser observada são os argumentos <code>*args</code> e <code>**kwargs</code> no <code>__init__</code> da classe <code>Autenticavel</code>, eles são usados uma vez que não se sabe todos os argumentos que o <code>__init__</code> da classe que estenderá o <code>Autenticavel</code> espera receber, funcionando de forma dinâmica (mais sobre esse recurso pode ser visto na <a href="https://docs.python.org/pt-br/3/tutorial/controlflow.html#more-on-defining-functions">documentação do Python</a>).</p>
<p>A segunda coisa a ser verificada é que para a classe <code>PessoaAutenticavel</code>, agora cria em seus objetos, a estrutura tanto da classe <code>Pessoa</code>, quanto <code>Autenticavel</code>. Algo similar a versão sem orientação a objetos a baixo:</p>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: pessoa_autenticavel.py</span>
<span class="kn">import</span> <span class="nn">autenticavel</span>
<span class="kn">import</span> <span class="nn">pessoa</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="n">pessoa</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">)</span>
<span class="n">autenticavel</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">)</span>
</pre></div>
<p>Também vale observar que as classes <code>PessoaAutenticavel</code> e <code>Sistema</code> não precisam definir nenhuma função, uma vez que elas cumprem seus papéis apenas herdando outras classes, porém seria possível implementar funções específicas dessas classes, assim como sobrescrever as funções definidas por outras classes.</p>
<h2>Ordem de resolução de métodos</h2>
<p>Embora herança múltiplas sejam interessantes, existe um problema, se ambas as classes pai possuírem uma função com um mesmo nome, a classe filha deveria chamar qual das funções? A do primeiro pai? A do último? Para lidar com esse problema o Python usa o MRO (<em>method resolution order</em>, ordem de resolução do método), que consiste em uma tupla com a ordem de qual classe o Python usará para encontrar o método a ser chamado. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="nb">print</span><span class="p">(</span><span class="n">PessoaAutenticavel</span><span class="o">.</span><span class="vm">__mro__</span><span class="p">)</span>
<span class="c1"># (<class '__main__.PessoaAutenticavel'>, <class '__main__.Autenticavel'>, <class '__main__.Pessoa'>, <class 'object'>)</span>
</pre></div>
<p>Por esse motivo que também foi possível chamar o <code>super().__init__</code> dentro de <code>Autenticavel</code>, que devido ao MRO, o Python chama o <code>__init__</code> da outra classe pai da classe que estendeu <code>Autenticavel</code>, em vez de precisar fazer um método <code>__init__</code> em <code>PessoaAutenticavel</code> chamando o <code>__init__</code> de todas as suas classes pais, como foi feito na versão sem orientação a objetos. E por isso a ordem <code>Autenticavel</code> e <code>Pessoa</code> na herança de <code>PessoaAutenticavel</code>, para fazer o MRO procurar os métodos primeiro em <code>Autenticavel</code> e depois em <code>Pessoa</code>.</p>
<p>Para tentar fugir da complexidade que pode ser herança múltipla, é possível escrever classes que tem por objetivo unicamente incluir alguma funcionalidade em outra, como o caso da classe <code>Autenticavel</code>, que pode ser herdada por qualquer outra classe do sistema para permitir o acesso ao sistema. Essas classes recebem o nome de mixins, e adiciona uma funcionalidade bem definida.</p>
<h2>Estendendo mixins</h2>
<p>Imagine se além de permitir o acesso ao sistema, também gostaríamos de registrar algumas tentativas de acesso, informando quando houve a tentativa e se o acesso foi concedido ou não. Como <code>Autenticavel</code> é uma classe, é possível extendê-la para implementar essa funcionalidade na função <code>autenticar</code>. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">datetime</span> <span class="kn">import</span> <span class="n">datetime</span>
<span class="k">class</span> <span class="nc">AutenticavelComRegistro</span><span class="p">(</span><span class="n">Autenticavel</span><span class="p">):</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">_get_data</span><span class="p">():</span>
<span class="k">return</span> <span class="n">datetime</span><span class="o">.</span><span class="n">now</span><span class="p">()</span><span class="o">.</span><span class="n">strftime</span><span class="p">(</span><span class="s1">'</span><span class="si">%d</span><span class="s1">/%m/%Y %T'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">autenticar</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'{self._get_data()} Tentativa de acesso de </span><span class="si">{usuario}</span><span class="s1">'</span><span class="p">)</span>
<span class="n">acesso</span> <span class="o">=</span> <span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="n">autenticar</span><span class="p">(</span><span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">)</span>
<span class="k">if</span> <span class="n">acesso</span><span class="p">:</span>
<span class="n">acesso_str</span> <span class="o">=</span> <span class="s1">'permitido'</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">acesso_str</span> <span class="o">=</span> <span class="s1">'negado'</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'{self._get_data()} Acesso de </span><span class="si">{usuario}</span><span class="s1"> </span><span class="si">{acesso_str}</span><span class="s1">'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">acesso</span>
<span class="k">class</span> <span class="nc">PessoaAutenticavelComRegistro</span><span class="p">(</span><span class="n">AutenticavelComRegistro</span><span class="p">,</span> <span class="n">Pessoa</span><span class="p">):</span>
<span class="o">...</span>
<span class="k">class</span> <span class="nc">SistemaAutenticavelComRegistro</span><span class="p">(</span><span class="n">AutenticavelComRegistro</span><span class="p">,</span> <span class="n">Sistema</span><span class="p">):</span>
<span class="o">...</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">PessoaAutenticavelComRegistro</span><span class="p">(</span>
<span class="n">nome</span><span class="o">=</span><span class="s1">'João'</span><span class="p">,</span> <span class="n">sobrenome</span><span class="o">=</span><span class="s1">'da Silva'</span><span class="p">,</span> <span class="n">idade</span><span class="o">=</span><span class="mi">20</span><span class="p">,</span>
<span class="n">usuario</span><span class="o">=</span><span class="s1">'joao'</span><span class="p">,</span> <span class="n">senha</span><span class="o">=</span><span class="s1">'secreta'</span><span class="p">,</span>
<span class="p">)</span>
<span class="n">p</span><span class="o">.</span><span class="n">autenticar</span><span class="p">(</span><span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">)</span>
<span class="c1"># Saída na tela:</span>
<span class="c1"># 23/04/2021 16:56:58 Tentativa de acesso de joao</span>
<span class="c1"># 23/04/2021 16:56:58 Acesso de joao permitido</span>
</pre></div>
<p>Essa implementação utiliza-se do <code>super()</code> para acessar a função <code>autenticar</code> da classe <code>Autenticavel</code> para não precisar reimplementar a autenticação. Porém, antes de chamá-la, manipula seus argumentos para registrar quem tentou acessar o sistema, assim como também manipula o seu retorno para registrar se o acesso foi permitido ou não.</p>
<p>Essa classe também permite analisar melhor a ordem em que as classes são consultadas quando uma função é chamada:</p>
<div class="highlight"><pre><span></span><span class="nb">print</span><span class="p">(</span><span class="n">PessoaAutenticavelComRegistro</span><span class="o">.</span><span class="vm">__mro__</span><span class="p">)</span>
<span class="c1"># (<class '__main__.PessoaAutenticavelComRegistro'>, <class '__main__.AutenticavelComRegistro'>, <class '__main__.Autenticavel'>, <class '__main__.Pessoa'>, <class 'object'>)</span>
</pre></div>
<p>Que também pode ser visto na forma de um digrama de classes:</p>
<p><img alt="Diagrama de classes" src="https://pythonclub.com.br/feeds/images/eduardoklosowski/oo-de-outra-forma-4/mro.png" /></p>
<p>Onde é feito uma <a href="https://pt.wikipedia.org/wiki/Busca_em_profundidade">busca em profundidade</a>, como se a função fosse chamada no primeiro pai, e só se ela não for encontrada, busca-se no segundo pai e assim por diante. Também é possível observar a classe <code>object</code>, que sempre será a última classe, e é a classe pai de todas as outras classes do Python quando elas não possuirem um pai declarado explicitamente.</p>
<h2>Considerações</h2>
<p>Herança múltipla pode dificultar bastante o entendimento do código, principalmente para encontrar onde determinada função está definida, porém pode facilitar bastante o código. Um exemplo que usa bastante herança e mixins são as <em>views</em> baseadas em classe do django (<a href="https://docs.djangoproject.com/pt-br/3.2/topics/class-based-views/"><em>class-based views</em></a>), porém para facilitar a visualização existe o site <a href="https://ccbv.co.uk/">Classy Class-Based Views</a> que lista todas as classes, e os mixins utilizados em cada uma, como pode ser visto em "Ancestors" como na <a href="https://ccbv.co.uk/projects/Django/3.1/django.views.generic.edit/UpdateView/">UpdateView</a>, que é usado para criar uma página com formulário para editar um registro já existente no banco, assim ela usa mixins para pegar um objeto do banco (<code>SingleObjectMixin</code>), processar formulário baseado em uma tabela do banco (<code>ModelFormMixin</code>) e algumas outras funcionalidades necessárias para implementar essa página.</p>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2021-05-03T18:00:00+00:00
Eduardo Klosowski
-
PythonClub: Orientação a objetos de outra forma: Herança
https://pythonclub.com.br/oo-de-outra-forma-3.html
<p>Algo que ajuda no desenvolvimento é a reutilização de código. Em orientação a objetos, essa reutilização pode ocorrer através de herança, onde um objeto pode se comportar como um objeto da sua própria classe, como também da classe que herdou.</p>
<h2>Adicionando funcionalidades</h2>
<p>Uma das utilidades da herança é estender uma classe para adicionar funcionalidades. Pensando no contexto das postagens anteriores, poderíamos querer criar um usuário e senha para algumas pessoas poderem acessar o sistema. Isso poderia ser feito adicionando atributos usuário e senha para as pessoas, além de uma função para validar se os dados estão corretos, e assim permitir o acesso ao sistema. Porém isso não pode ser feito para todas as pessoas, e sim apenas para aqueles que possuem permissão de acesso.</p>
<h3>Sem orientação a objetos</h3>
<p>Voltando a solução com dicionários (sem utilizar orientação a objetos), isso consistiria em criar um dicionário com a estrutura de uma pessoa, e em seguida estender essa estrutura com os novos campos de usuário e senha nesse mesmo dicionário, algo como:</p>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: pessoa.py</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">pessoa</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'nome'</span><span class="p">]</span> <span class="o">=</span> <span class="n">nome</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'sobrenome'</span><span class="p">]</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'idade'</span><span class="p">]</span> <span class="o">=</span> <span class="n">idade</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="n">pessoa</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{pessoa['nome']}</span><span class="s2"> </span><span class="si">{pessoa['sobrenome']}</span><span class="s2">"</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: pessoa_autenticavel.py</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">pessoa</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'usuario'</span><span class="p">]</span> <span class="o">=</span> <span class="n">usuario</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'senha'</span><span class="p">]</span> <span class="o">=</span> <span class="n">senha</span>
<span class="k">def</span> <span class="nf">autenticar</span><span class="p">(</span><span class="n">pessoa</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="k">return</span> <span class="n">pessoa</span><span class="p">[</span><span class="s1">'usuario'</span><span class="p">]</span> <span class="o">==</span> <span class="n">usuario</span> <span class="ow">and</span> <span class="n">pessoa</span><span class="p">[</span><span class="s1">'senha'</span><span class="p">]</span> <span class="o">==</span> <span class="n">senha</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pessoa</span>
<span class="kn">import</span> <span class="nn">pessoa_autenticavel</span>
<span class="n">p</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">pessoa</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="n">pessoa_autenticavel</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">pessoa</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">(</span><span class="n">p</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="n">pessoa_autenticavel</span><span class="o">.</span><span class="n">autenticar</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">))</span>
</pre></div>
<p>Porém nessa solução é possível que o programador esqueça de chamar as duas funções <code>init</code> diferentes, e como queremos que todo dicionário com a estrutura de <code>pessoa_autenticavel</code> contenha também a estrutura de <code>pessoa</code>, podemos chamar o <code>init</code> de pessoa dentro do <code>init</code> de <code>pessoa_autenticavel</code>:</p>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: pessoa_autenticavel.py</span>
<span class="kn">import</span> <span class="nn">pessoa</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="n">pessoa</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">)</span>
<span class="n">p</span><span class="p">[</span><span class="s1">'usuario'</span><span class="p">]</span> <span class="o">=</span> <span class="n">usuario</span>
<span class="n">p</span><span class="p">[</span><span class="s1">'senha'</span><span class="p">]</span> <span class="o">=</span> <span class="n">senha</span>
<span class="o">...</span> <span class="c1"># Demais funções</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pessoa</span>
<span class="kn">import</span> <span class="nn">pessoa_autenticavel</span>
<span class="n">p</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">pessoa_autenticavel</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">pessoa</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">(</span><span class="n">p</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="n">pessoa_autenticavel</span><span class="o">.</span><span class="n">autenticar</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">))</span>
</pre></div>
<p>Nesse caso foi necessário alterar o nome do argumento <code>pessoa</code> da função <code>pessoa_autenticavel.init</code> para não conflitar com o outro módulo importado com esse mesmo nome. Porém ao chamar um <code>init</code> dentro de outro, temos a garantia de que o dicionário será compatível tanto com a estrutura pedida para ser criada pelo programador, quanto pelas estruturas pais dela.</p>
<h3>Com orientação a objetos</h3>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Pessoa</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nome</span> <span class="o">=</span> <span class="n">nome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">idade</span> <span class="o">=</span> <span class="n">idade</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{self.nome}</span><span class="s1"> </span><span class="si">{self.sobrenome}</span><span class="s1">'</span>
<span class="k">class</span> <span class="nc">PessoaAutenticavel</span><span class="p">(</span><span class="n">Pessoa</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="n">Pessoa</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">=</span> <span class="n">usuario</span>
<span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">=</span> <span class="n">senha</span>
<span class="k">def</span> <span class="nf">autenticar</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">==</span> <span class="n">usuario</span> <span class="ow">and</span> <span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">==</span> <span class="n">senha</span>
<span class="n">p</span> <span class="o">=</span> <span class="n">PessoaAutenticavel</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">Pessoa</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">(</span><span class="n">p</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="n">PessoaAutenticavel</span><span class="o">.</span><span class="n">autenticar</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">))</span>
</pre></div>
<p>A principal novidade desse exemplo é que ao declarar a classe <code>PessoaAutenticavel</code> (filha), foi declarado a classe <code>Pessoa</code> (pai) entre parênteses, isso faz o interpretador Python criar uma cópia dessa classe estendendo-a com as novas funções que estamos criando. Porém pode ser um pouco redundante chamar <code>Pessoa.__init__</code> dentro da função <code>__init__</code> sendo que já foi declarado que ela estende <code>Pessoa</code>, podendo ser trocado por <code>super()</code>, que aponta para a classe que foi estendida. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">PessoaAutenticavel</span><span class="p">(</span><span class="n">Pessoa</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">,</span> <span class="n">usuario</span><span class="p">,</span> <span class="n">senha</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">usuario</span> <span class="o">=</span> <span class="n">usuario</span>
<span class="bp">self</span><span class="o">.</span><span class="n">senha</span> <span class="o">=</span> <span class="n">senha</span>
<span class="o">...</span> <span class="c1"># Demais funções</span>
</pre></div>
<p>Assim se evita repetir o nome da classe, e já passa automaticamente a referência para <code>self</code>, assim como quando usamos o açúcar sintático apresentado na primeira postagem dessa série. E esse açúcar sintática também pode ser usado para chamar tanto as funções declaradas em <code>Pessoa</code> quanto em <code>PessoaAutenticavel</code>. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="n">p</span> <span class="o">=</span> <span class="n">PessoaAutenticavel</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">())</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p</span><span class="o">.</span><span class="n">autenticar</span><span class="p">(</span><span class="s1">'joao'</span><span class="p">,</span> <span class="s1">'secreta'</span><span class="p">))</span>
</pre></div>
<p>Esse método também facilita a utilização das funções, uma vez que não é necessário lembrar em qual classe que cada função foi declarada. Na verdade, como <code>PessoaAutenticavel</code> estende <code>Pessoa</code>, seria possível executar também <code>PessoaAutenticavel.nome_completo</code>, porém eles apontam para a mesma função.</p>
<h2>Sobrescrevendo uma função</h2>
<p>A classe <code>Pessoa</code> possui a função <code>nome_completo</code> que retorna uma <code>str</code> contento nome e sobrenome. Porém no Japão, assim como em outros países asiáticos, o sobrenome vem primeiro, e até <a href="https://noticias.uol.com.br/ultimas-noticias/efe/2019/06/24/japao-quer-voltar-a-ordem-tradicional-dos-nomes-abe-shinzo-nao-shinzo-abe.htm">estão pedindo para seguir a tradição deles ao falarem os nomes de japoneses</a>, como o caso do primeiro-ministro, mudando de Shinzo Abe para Abe Shinzo.</p>
<h3>Com orientação a objetos</h3>
<p>Isso também pode ser feito no sistema usando herança, porém em vez de criar uma nova função com outro nome, é possível criar uma função com o mesmo nome, sobrescrevendo a anterior, porém apenas para os objetos da classe filha. Algo semelhante ao que já foi feito com a função <code>__init__</code>. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Japones</span><span class="p">(</span><span class="n">Pessoa</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{self.sobrenome}</span><span class="s1"> </span><span class="si">{self.nome}</span><span class="s1">'</span>
<span class="n">p1</span> <span class="o">=</span> <span class="n">Pessoa</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="n">p2</span> <span class="o">=</span> <span class="n">Japones</span><span class="p">(</span><span class="s1">'Shinzo'</span><span class="p">,</span> <span class="s1">'Abe'</span><span class="p">,</span> <span class="mi">66</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">())</span> <span class="c1"># João da Silva</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p2</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">())</span> <span class="c1"># Abe Shinzo</span>
</pre></div>
<p>Essa relação de herança traz algo interessante, todo objeto da classe <code>Japones</code> se comporta como um objeto da classe <code>Pessoa</code>, porém a relação inversa não é verdade. Assim como podemos dizer que todo japonês é uma pessoa, mas nem todas as pessoas são japonesas. Ser japonês é um caso mais específico de pessoa, assim como as demais nacionalidades.</p>
<h3>Sem orientação a objetos</h3>
<p>Esse comportamento de sobrescrever a função <code>nome_completo</code> não é tão simples de replicar em uma estrutura de dicionário, porém é possível fazer. Porém como uma pessoa pode ser tanto japonês quanto não ser, não é possível saber de antemão para escrever no código <code>pessoa.nome_completo</code> ou <code>japones.nome_completo</code>, que diferente do exemplo da autenticação, agora são duas funções diferentes, isso precisa ser descoberto dinamicamente quando se precisar chamar a função.</p>
<p>Uma forma de fazer isso é guardar uma referência para a função que deve ser chamada dentro da própria estrutura. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: pessoa.py</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">pessoa</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'nome'</span><span class="p">]</span> <span class="o">=</span> <span class="n">nome</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'sobrenome'</span><span class="p">]</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'idade'</span><span class="p">]</span> <span class="o">=</span> <span class="n">idade</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'nome_completo'</span><span class="p">]</span> <span class="o">=</span> <span class="n">nome_completo</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="n">pessoa</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{pessoa['nome']}</span><span class="s2"> </span><span class="si">{pessoa['sobrenome']}</span><span class="s2">"</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: japones.py</span>
<span class="kn">import</span> <span class="nn">pessoa</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">japones</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="n">pessoa</span><span class="p">(</span><span class="n">japones</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">)</span>
<span class="n">japones</span><span class="p">[</span><span class="s1">'nome_completo'</span><span class="p">]</span> <span class="o">=</span> <span class="n">nome_completo</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="n">japones</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{pessoa['sobrenome']}</span><span class="s2"> </span><span class="si">{pessoa['nome']}</span><span class="s2">"</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pessoa</span>
<span class="kn">import</span> <span class="nn">japones</span>
<span class="n">p1</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">pessoa</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">p1</span><span class="p">,</span> <span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="n">p2</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">japones</span><span class="o">.</span><span class="n">init</span><span class="p">(</span><span class="n">p2</span><span class="p">,</span> <span class="s1">'Shinzo'</span><span class="p">,</span> <span class="s1">'Abe'</span><span class="p">,</span> <span class="mi">66</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="p">[</span><span class="s1">'nome_completo'</span><span class="p">](</span><span class="n">p1</span><span class="p">))</span> <span class="c1"># João da Silva</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p2</span><span class="p">[</span><span class="s1">'nome_completo'</span><span class="p">](</span><span class="n">p2</span><span class="p">))</span> <span class="c1"># Abe Shinzo</span>
</pre></div>
<p>Perceba que a forma de chamar a função foi alterada. O que acontece na prática é que toda função que pode ser sobrescrita não é chamada diretamente, e sim a partir de uma referência, e isso gera um custo computacional adicional. Como esse custo não é tão alto (muitas vezes sendo quase irrelevante), esse é o comportamento adotado em várias linguagens, porém em C++, por exemplo, existe a palavra-chave <code>virtual</code> para descrever quando uma função pode ser sobrescrita ou não.</p>
<h2>Considerações</h2>
<p>Herança é um mecanismo interessante para ser explorado com o objetivo de reaproveitar código e evitar repeti-lo. Porém isso pode vir com alguns custos, seja computacional durante sua execução, seja durante a leitura do código, sendo necessário verificar diversas classes para saber o que de fato está sendo executado, porém isso também pode ser usado para ocultar e abstrair lógicas mais complicadas, como eu já comentei em outra <a href="https://dev.to/acaverna/encapsulamento-da-logica-do-algoritmo-298e">postagem</a>.</p>
<p>Herança também permite trabalhar com generalização e especialização, podendo descrever o comportamento mais geral, ou mais específico. Ou simplesmente só adicionar mais funcionalidades a uma classe já existente.</p>
<p>Assim como foi utilizado o <code>super()</code> para chamar a função <code>__init__</code> da classe pai, é possível utilizá-lo para chamar qualquer outra função. Isso permite, por exemplo, tratar os argumentos da função, aplicando modificações antes de chamar a função original, ou seu retorno, executando algum processamento em cima do retorno dela, não precisando rescrever toda a função.</p>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2021-04-26T20:00:00+00:00
Eduardo Klosowski
-
PythonClub: Orientação a objetos de outra forma: Métodos estáticos e de classes
https://pythonclub.com.br/oo-de-outra-forma-2.html
<p>Na <a href="https://dev.to/acaverna/orientacao-a-objetos-de-outra-forma-classes-e-objetos-3mfd">postagem anterior</a> foi apresentado o <code>self</code>, nessa postagem será discutido mais a respeito desse argumento, considerando opções para ele e suas aplicações.</p>
<h2>Métodos estáticos</h2>
<p>Nem todas as funções de uma classe precisam receber uma referência de um objeto para lê-lo ou alterá-lo, muitas vezes uma função pode fazer o seu papel apenas com os dados passados como argumento, por exemplo, receber um nome e validar se ele possui pelo menos três caracteres sem espaço. Dessa forma, essa função poderia ser colocada fora do escopo da classe, porém para facilitar sua chamada, e possíveis alterações (que será discutido em outra postagem), é possível colocar essa função dentro da classe e informar que ela não receberá o argumento <code>self</code> com o decorador <code>@staticmethod</code>:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Pessoa</span><span class="p">:</span>
<span class="o">...</span> <span class="c1"># Demais funções</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">valida_nome</span><span class="p">(</span><span class="n">nome</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">nome</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">3</span> <span class="ow">and</span> <span class="s1">' '</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">nome</span>
</pre></div>
<p>Dessa forma, essa função pode ser chamada diretamente de um objeto pessoa, ou até mesmo diretamente da classe, sem precisar criar um objeto primeiro:</p>
<div class="highlight"><pre><span></span><span class="c1"># Chamando diretamente da classe</span>
<span class="nb">print</span><span class="p">(</span><span class="n">Pessoa</span><span class="o">.</span><span class="n">valida_nome</span><span class="p">(</span><span class="s1">'João'</span><span class="p">))</span>
<span class="c1"># Chamando através de um objeto do tipo Pessoa</span>
<span class="n">p1</span> <span class="o">=</span> <span class="n">Pessoa</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="o">.</span><span class="n">valida_nome</span><span class="p">(</span><span class="n">p1</span><span class="o">.</span><span class="n">nome</span><span class="p">))</span>
</pre></div>
<p>E essa função também pode ser utilizada dendro de outras funções, como validar o nome na criação de uma pessoa, de forma que caso o nome informado seja válido, será criado um objeto do tipo Pessoa, e caso o nome seja inválido, será lançado uma exceção:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Pessoa</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">valida_nome</span><span class="p">(</span><span class="n">nome</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s1">'Nome inválido'</span><span class="p">)</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nome</span> <span class="o">=</span> <span class="n">nome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">idade</span> <span class="o">=</span> <span class="n">idade</span>
<span class="o">...</span> <span class="c1"># Demais funções</span>
<span class="nd">@staticmethod</span>
<span class="k">def</span> <span class="nf">valida_nome</span><span class="p">(</span><span class="n">nome</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">len</span><span class="p">(</span><span class="n">nome</span><span class="p">)</span> <span class="o">>=</span> <span class="mi">3</span> <span class="ow">and</span> <span class="s1">' '</span> <span class="ow">not</span> <span class="ow">in</span> <span class="n">nome</span>
<span class="n">p1</span> <span class="o">=</span> <span class="n">Pessoa</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="c1"># Cria objeto</span>
<span class="n">p2</span> <span class="o">=</span> <span class="n">Pessoa</span><span class="p">(</span><span class="s1">'a'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="c1"># Lança ValueError: Nome inválido</span>
</pre></div>
<h2>Métodos da classe</h2>
<p>Entretanto algumas funções podem precisar de um meio termo, necessitar acessar o contexto da classe, porém sem necessitar de um objeto. Isso é feito através do decorador <code>@classmethod</code>, onde a função decorada com ele, em vez de receber um objeto como primeiro argumento, recebe a própria classe.</p>
<p>Para demonstrar essa funcionalidade será implementado um <em>id</em> auto incremental para os objetos da classe <code>Pessoa</code>:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Pessoa</span><span class="p">:</span>
<span class="n">total_de_pessoas</span> <span class="o">=</span> <span class="mi">0</span>
<span class="nd">@classmethod</span>
<span class="k">def</span> <span class="nf">novo_id</span><span class="p">(</span><span class="bp">cls</span><span class="p">):</span>
<span class="bp">cls</span><span class="o">.</span><span class="n">total_de_pessoas</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">return</span> <span class="bp">cls</span><span class="o">.</span><span class="n">total_de_pessoas</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">id</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">novo_id</span><span class="p">()</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nome</span> <span class="o">=</span> <span class="n">nome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">idade</span> <span class="o">=</span> <span class="n">idade</span>
<span class="n">p1</span> <span class="o">=</span> <span class="n">Pessoa</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="o">.</span><span class="n">id</span><span class="p">)</span> <span class="c1"># Imprime 1</span>
<span class="n">p2</span> <span class="o">=</span> <span class="n">Pessoa</span><span class="p">(</span><span class="s1">'Maria'</span><span class="p">,</span> <span class="s1">'dos Santos'</span><span class="p">,</span> <span class="mi">18</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p2</span><span class="o">.</span><span class="n">id</span><span class="p">)</span> <span class="c1"># Imprime 2</span>
<span class="nb">print</span><span class="p">(</span><span class="n">Pessoa</span><span class="o">.</span><span class="n">total_de_pessoas</span><span class="p">)</span> <span class="c1"># Imprime 2</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="o">.</span><span class="n">total_de_pessoas</span><span class="p">)</span> <span class="c1"># Imprime 2</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p2</span><span class="o">.</span><span class="n">total_de_pessoas</span><span class="p">)</span> <span class="c1"># Imprime 2</span>
</pre></div>
<p>Nesse código é criado uma variável <code>total_de_pessoas</code> dentro do escopo da classe <code>Pessoas</code>, e que é compartilhado tanto pela classe, como pelos objetos dessa classe, diferente de declará-la com <code>self.</code> dentro do <code>__init__</code>, onde esse valor pertenceria apenas ao objeto, e não é compartilhado com os demais objetos. Declarar variáveis dentro do contexto da classe é similar ao se declarar variáveis com <code>static</code> em outras linguagens, assim como o <code>@classmethod</code> é semelhante a declaração de funções com <code>static</code>.</p>
<p>As funções declaradas com <code>@classmethod</code> também podem ser chamadas sem a necessidade de se criar um objeto, como <code>Pessoa.novo_id()</code>, embora que para essa função específica isso não faça muito sentido, ou receber outros argumentos, tudo depende do que essa função fará.</p>
<h2>Considerações</h2>
<p>Embora possa parecer confuso identificar a diferença de uma função de um objeto (função sem decorador), função de uma classe (com decorador <code>@classmethod</code>) e função sem acesso a nenhum outro contexto (com decorador <code>@staticmethod</code>), essa diferença fica mais clara ao se analisar o primeiro argumento recebido por cada tipo de função. Podendo ser a referência a um objeto (<code>self</code>) e assim necessitando que um objeto seja criado anteriormente, ser uma classe (<code>cls</code>) e não necessitando receber um objeto, ou simplesmente não recebendo nenhum argumento especial, apenas os demais argumentos necessários para a função. Sendo diferenciados pelo uso dos decoradores.</p>
<p>Na orientação a objetos implementada pelo Python, algumas coisas podem ficar confusas quando se mistura com nomenclaturas de outras linguagens que possuem implementações diferentes. A linguagem Java, por exemplo, utiliza a palavra-chave <code>static</code> para definir os atributos e métodos de classe, enquanto no Python um método estático é aquele que não acessa nem um objeto, nem uma classe, devendo ser utilizado o escopo da classe e o decorador <code>@classmethod</code> para se criar atributos e métodos da classe.</p>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2021-04-19T20:00:00+00:00
Eduardo Klosowski
-
PythonClub: Orientação a objetos de outra forma: Classes e objetos
https://pythonclub.com.br/oo-de-outra-forma-1.html
<p>Nas poucas e raríssimas lives que eu fiz na <a href="https://www.twitch.tv/eduardoklosowski">Twitch</a>, surgiu a ideia de escrever sobre programação orientada a objetos em <a href="https://www.python.org/">Python</a>, principalmente por algumas diferenças de como ela foi implementada nessa linguagem. Aproveitando o tema, vou fazer uma série de postagens dando uma visão diferente sobre orientação a objetos. E nessa primeira postagem falarei sobre classes e objetos.</p>
<h2>Usando um dicionário</h2>
<p>Entretanto, antes de começar com orientação a objetos, gostaria de apresentar e discutir alguns exemplos sem utilizar esse paradigma de programação.</p>
<p>Pensando em um sistema que precise manipular dados de pessoas, é possível utilizar os <a href="https://docs.python.org/pt-br/3/library/stdtypes.html#mapping-types-dict">dicionários</a> do Python para agrupar os dados de uma pessoa em uma única variável, como no exemplo a baixo:</p>
<div class="highlight"><pre><span></span><span class="n">pessoa</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'nome'</span><span class="p">:</span> <span class="s1">'João'</span><span class="p">,</span>
<span class="s1">'sobrenome'</span><span class="p">:</span> <span class="s1">'da Silva'</span><span class="p">,</span>
<span class="s1">'idade'</span><span class="p">:</span> <span class="mi">20</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>Onde os dados poderiam ser acessados através da variável e do nome do dado desejado, como:</p>
<div class="highlight"><pre><span></span><span class="nb">print</span><span class="p">(</span><span class="n">pessoa</span><span class="p">[</span><span class="s1">'nome'</span><span class="p">])</span> <span class="c1"># Imprimindo João</span>
</pre></div>
<p>Assim, todos os dados de uma pessoa ficam agrupados em uma variável, o que facilita bastante a programação, visto que não é necessário criar uma variável para cada dado, e quando se manipula os dados de diferentes pessoas fica muito mais fácil identificar de qual pessoa aquele dado se refere, bastando utilizar variáveis diferentes.</p>
<h3>Função para criar o dicionário</h3>
<p>Apesar de prático, é necessário replicar essa estrutura de dicionário toda vez que se desejar utilizar os dados de uma nova pessoa. Para evitar a repetição de código, a criação desse dicionário pode ser feita dentro de uma função que pode ser colocada em um módulo <code>pessoa</code> (arquivo, nesse caso com o nome de <code>pessoa.py</code>):</p>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: pessoa.py</span>
<span class="k">def</span> <span class="nf">nova</span><span class="p">(</span><span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="k">return</span> <span class="p">{</span>
<span class="s1">'nome'</span><span class="p">:</span> <span class="n">nome</span><span class="p">,</span>
<span class="s1">'sobrenome'</span><span class="p">:</span> <span class="n">sobrenome</span><span class="p">,</span>
<span class="s1">'idade'</span><span class="p">:</span> <span class="n">idade</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>E para criar o dicionário que representa uma pessoa, basta importar esse módulo (arquivo) e chamar a função <code>nova</code>:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pessoa</span>
<span class="n">p1</span> <span class="o">=</span> <span class="n">pessoa</span><span class="o">.</span><span class="n">nova</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="n">p2</span> <span class="o">=</span> <span class="n">pessoa</span><span class="o">.</span><span class="n">nova</span><span class="p">(</span><span class="s1">'Maria'</span><span class="p">,</span> <span class="s1">'dos Santos'</span><span class="p">,</span> <span class="mi">18</span><span class="p">)</span>
</pre></div>
<p>Desta forma, garante-se que todos os dicionários representando pessoas terão os campos desejados e devidamente preenchidos.</p>
<h3>Função com o dicionário</h3>
<p>Também é possível criar algumas funções para executar operações com os dados desses dicionários, como pegar o nome completo da pessoa, trocar o seu sobrenome, ou fazer aniversário (o que aumentaria a idade da pessoa em um ano):</p>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: pessoa.py</span>
<span class="k">def</span> <span class="nf">nova</span><span class="p">(</span><span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="o">...</span> <span class="c1"># Código abreviado</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="n">pessoa</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s2">"</span><span class="si">{pessoa['nome']}</span><span class="s2"> </span><span class="si">{pessoa['sobrenome']}</span><span class="s2">"</span>
<span class="k">def</span> <span class="nf">trocar_sobrenome</span><span class="p">(</span><span class="n">pessoa</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">):</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'sobrenome'</span><span class="p">]</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="k">def</span> <span class="nf">fazer_aniversario</span><span class="p">(</span><span class="n">pessoa</span><span class="p">):</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'idade'</span><span class="p">]</span> <span class="o">+=</span> <span class="mi">1</span>
</pre></div>
<p>E sendo usado como:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pessoa</span>
<span class="n">p1</span> <span class="o">=</span> <span class="n">pessoa</span><span class="o">.</span><span class="n">nova</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="n">pessoa</span><span class="o">.</span><span class="n">trocar_sobrenome</span><span class="p">(</span><span class="n">p1</span><span class="p">,</span> <span class="s1">'dos Santos'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">pessoa</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">(</span><span class="n">p1</span><span class="p">))</span>
<span class="n">pessoa</span><span class="o">.</span><span class="n">fazer_aniversario</span><span class="p">(</span><span class="n">p1</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="p">[</span><span class="s1">'idade'</span><span class="p">])</span>
</pre></div>
<p>Nesse caso, pode-se observar que todas as funções aqui implementadas seguem o padrão de receber o dicionário que representa a pessoa como primeiro argumento, podendo ter outros argumentos ou não conforme a necessidade, acessando e alterando os valores desse dicionário.</p>
<h2>Versão com orientação a objetos</h2>
<p>Antes de entrar na versão orientada a objetos propriamente dita dos exemplos anteriores, vou fazer uma pequena alteração para facilitar o entendimento posterior. A função <code>nova</code> será separada em duas partes, a primeira que criará um dicionário, e chamará uma segunda função (<code>init</code>), que receberá esse dicionário como primeiro argumento (seguindo o padrão das demais funções) e criará sua estrutura com os devidos valores.</p>
<div class="highlight"><pre><span></span><span class="c1"># Arquivo: pessoa.py</span>
<span class="k">def</span> <span class="nf">init</span><span class="p">(</span><span class="n">pessoa</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'nome'</span><span class="p">]</span> <span class="o">=</span> <span class="n">nome</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'sobrenome'</span><span class="p">]</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="n">pessoa</span><span class="p">[</span><span class="s1">'idade'</span><span class="p">]</span> <span class="o">=</span> <span class="n">idade</span>
<span class="k">def</span> <span class="nf">nova</span><span class="p">(</span><span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="n">pessoa</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">init</span><span class="p">(</span><span class="n">pessoa</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">)</span>
<span class="k">return</span> <span class="n">pessoa</span>
<span class="o">...</span> <span class="c1"># Demais funções do arquivo</span>
</pre></div>
<p>Porém isso não muda a forma de uso:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">pessoa</span>
<span class="n">p1</span> <span class="o">=</span> <span class="n">pessoa</span><span class="o">.</span><span class="n">nova</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
</pre></div>
<h3>Função para criar uma pessoa</h3>
<p>A maioria das linguagens de programação que possuem o paradigma de programação orientado a objetos faz o uso de classes para definir a estrutura dos objetos. O Python também utiliza classes, que podem ser definidas com a palavra-chave <code>class</code> seguidas de um nome para ela. E dentro dessa estrutura, podem ser definidas funções para manipular os objetos daquela classe, que em algumas linguagens também são chamadas de métodos (funções declaradas dentro do escopo uma classe).</p>
<p>Para converter o dicionário para uma classe, o primeiro passo é implementar uma função para criar a estrutura desejada. Essa função deve possui o nome <code>__init__</code>, e é bastante similar a função <code>init</code> do código anterior:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Pessoa</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nome</span> <span class="o">=</span> <span class="n">nome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">idade</span> <span class="o">=</span> <span class="n">idade</span>
</pre></div>
<p>As diferenças são que agora o primeiro parâmetro se chama <code>self</code>, que é um padrão utilizado no Python, e em vez de usar colchetes e aspas para acessar os dados, aqui basta utilizar o ponto e o nome do dado desejado (que aqui também pode ser chamado de atributo, visto que é uma variável do objeto). A função <code>nova</code> implementada anteriormente não é necessária, a própria linguagem cria um objeto e passa ele como primeiro argumento para o <code>__init__</code>. E assim para se criar um objeto da classe <code>Pessoa</code> basta chamar a classe como se fosse uma função, ignorando o argumento <code>self</code> e informando os demais, como se estivesse chamando a função <code>__init__</code> diretamente:</p>
<div class="highlight"><pre><span></span><span class="n">p1</span> <span class="o">=</span> <span class="n">Pessoa</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'da Silva'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
</pre></div>
<p>Nesse caso, como a própria classe cria um contexto diferente para as funções (escopo ou <em>namespace</em>), não está mais sendo utilizado arquivos diferentes, porém ainda é possível fazê-lo, sendo necessário apenas fazer o <code>import</code> adequado. Mas para simplificação, tanto a declaração da classe, como a criação do objeto da classe <code>Pessoa</code> podem ser feitas no mesmo arquivo, assim como os demais exemplos dessa postagem.</p>
<h3>Outras funções</h3>
<p>As demais funções feitas anteriormente para o dicionário também podem ser feitas na classe <code>Pessoa</code>, seguindo as mesmas diferenças já apontadas anteriormente:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">Pessoa</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">nome</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">,</span> <span class="n">idade</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">nome</span> <span class="o">=</span> <span class="n">nome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="bp">self</span><span class="o">.</span><span class="n">idade</span> <span class="o">=</span> <span class="n">idade</span>
<span class="k">def</span> <span class="nf">nome_completo</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="sa">f</span><span class="s1">'</span><span class="si">{self.nome}</span><span class="s1"> </span><span class="si">{self.sobrenome}</span><span class="s1">'</span>
<span class="k">def</span> <span class="nf">trocar_sobrenome</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">sobrenome</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">sobrenome</span> <span class="o">=</span> <span class="n">sobrenome</span>
<span class="k">def</span> <span class="nf">fazer_aniversario</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">idade</span> <span class="o">+=</span> <span class="mi">1</span>
</pre></div>
<p>Para se chamar essas funções, basta acessá-las através do contexto da classe, passando o objeto criado anteriormente como primeiro argumento:</p>
<div class="highlight"><pre><span></span><span class="n">p1</span> <span class="o">=</span> <span class="n">Pessoa</span><span class="p">(</span><span class="s1">'João'</span><span class="p">,</span> <span class="s1">'dos Santos'</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span>
<span class="n">Pessoa</span><span class="o">.</span><span class="n">trocar_sobrenome</span><span class="p">(</span><span class="n">p1</span><span class="p">,</span> <span class="s1">'dos Santos'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">Pessoa</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">(</span><span class="n">p1</span><span class="p">))</span>
<span class="n">Pessoa</span><span class="o">.</span><span class="n">fazer_aniversario</span><span class="p">(</span><span class="n">p1</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="o">.</span><span class="n">idade</span><span class="p">)</span>
</pre></div>
<p>Essa sintaxe é bastante semelhante a versão sem orientação a objetos implementada anteriormente. Porém quando se está utilizando objetos, é possível chamar essas funções com uma outra sintaxe, informando primeiro o objeto, seguido de ponto e o nome da função desejada, com a diferença de que não é mais necessário informar o objeto como primeiro argumento. Como a função foi chamada através de um objeto, o próprio Python se encarrega de passá-lo para o argumento <code>self</code>, sendo necessário informar apenas os demais argumentos:</p>
<div class="highlight"><pre><span></span><span class="n">p1</span><span class="o">.</span><span class="n">trocar_sobrenome</span><span class="p">(</span><span class="s1">'dos Santos'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="o">.</span><span class="n">nome_completo</span><span class="p">())</span>
<span class="n">p1</span><span class="o">.</span><span class="n">fazer_aniversario</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">p1</span><span class="o">.</span><span class="n">idade</span><span class="p">)</span>
</pre></div>
<p>Existem algumas diferenças entre as duas sintaxes, porém isso será tratado posteriormente. Por enquanto a segunda sintaxe pode ser vista como um <a href="https://pt.wikipedia.org/wiki/A%C3%A7%C3%BAcar_sint%C3%A1tico">açúcar sintático</a> da primeira, ou seja, uma forma mais rápida e fácil de fazer a mesma coisa que a primeira, e por isso sendo a recomendada.</p>
<h2>Considerações</h2>
<p>Como visto nos exemplos, programação orientada a objetos é uma técnica para juntar variáveis em uma mesma estrutura e facilitar a escrita de funções que seguem um determinado padrão, recebendo a estrutura como argumento, porém a sintaxe mais utilizada no Python para chamar as funções de um objeto (métodos) posiciona a variável que guarda a estrutura antes do nome da função, em vez do primeiro argumento.</p>
<p>No Python, o argumento da estrutura ou objeto (<code>self</code>) aparece explicitamente como primeiro argumento da função, enquanto em outras linguagens essa variável pode receber outro nome (como <code>this</code>) e não aparece explicitamente nos argumentos da função, embora essa variável tenha que ser criada dentro do contexto da função para permitir manipular o objeto.</p>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2021-04-12T18:00:00+00:00
Eduardo Klosowski
-
PythonClub: Funções in place ou cópia de valor
https://pythonclub.com.br/funcao-inplace-ou-copia-de-valor.html
<p>Eventualmente observo dificuldades de algumas pessoas em usar corretamente alguma função, seja porque a função deveria ser executada isoladamente, e utilizado a própria variável que foi passada como argumento posteriormente, seja porque deveria se atribuir o retorno da função a alguma variável, e utilizar essa nova variável. No Python, essa diferença pode ser observada nos métodos das listas <code>sort</code> e <code>reverse</code> para as funções <code>sorted</code> e <code>reversed</code>, que são implementadas com padrões diferentes, <em>in place</em> e cópia de valor respectivamente. Assim pretendo discutir esses dois padrões de funções, comentando qual a diferença e o melhor caso de aplicação de cada padrão.</p>
<h2>Função de exemplo</h2>
<p>Para demonstrar como esses padrões funcionam, será implementado uma função que recebe uma lista e calcula o dobro dos valores dessa lista. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="n">entrada</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="c1"># Execução da função</span>
<span class="n">resultado</span> <span class="o">=</span> <span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">16</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">8</span><span class="p">]</span>
</pre></div>
<h3>Função com in place</h3>
<p>A ideia do padrão <em>in place</em> é alterar a própria variável recebida pela função (ou o próprio objeto, caso esteja lidando com orientação a objetos). Neste caso, bastaria calcular o dobro do valor de cada posição da lista, e sobrescrever a posição com seu resultado. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
<span class="k">def</span> <span class="nf">dobro_inplace</span><span class="p">(</span><span class="n">lista</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="kc">None</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="nb">len</span><span class="p">(</span><span class="n">lista</span><span class="p">)):</span>
<span class="n">lista</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">lista</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="n">valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">retorno</span> <span class="o">=</span> <span class="n">dobro_inplace</span><span class="p">(</span><span class="n">valores</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Variável: valores | Tipo: {type(valores)} | Valor: </span><span class="si">{valores}</span><span class="s1">'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Variável: retorno | Tipo: {type(retorno)} | Valor: </span><span class="si">{retorno}</span><span class="s1">'</span><span class="p">)</span>
</pre></div>
<p>Resultado da execução:</p>
<div class="highlight"><pre><span></span><span class="err">Variável: valores | Tipo: <class 'list'> | Valor: [10, 4, 16, 12, 8]</span>
<span class="err">Variável: retorno | Tipo: <class 'NoneType'> | Valor: None</span>
</pre></div>
<p>Com essa execução é possível observar que os valores da lista foram alterados, e que o retorno da função é nulo (<code>None</code>), ou seja, a função alterou a própria lista passada como argumento. Outro ponto importante a ser observado é a assinatura da função (tipo dos argumentos e do retorno da função), que recebe uma lista de inteiros e não tem retorno ou é nulo (<code>None</code>). Dessa forma embora seja possível chamar essa função diretamente quando está se informando os argumentos de outra função, como <code>print(dobro_inplace(valores))</code>, a função <code>print</code> receberia <code>None</code> e não a lista como argumento.</p>
<h3>Função com cópia de valor</h3>
<p>A ideia do padrão cópia de valor é criar uma cópia do valor passado como argumento e retornar essa cópia, sem alterar a variável recebida (ou criando um novo objeto, no caso de orientação a objetos). Neste caso, é necessário criar uma nova lista e adicionar nela os valores calculados. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
<span class="k">def</span> <span class="nf">dobro_copia</span><span class="p">(</span><span class="n">lista</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">]:</span>
<span class="n">nova_lista</span> <span class="o">=</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="nb">len</span><span class="p">(</span><span class="n">lista</span><span class="p">)):</span>
<span class="n">nova_lista</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">lista</span><span class="p">[</span><span class="n">i</span><span class="p">])</span>
<span class="k">return</span> <span class="n">nova_lista</span>
<span class="n">valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">retorno</span> <span class="o">=</span> <span class="n">dobro_copia</span><span class="p">(</span><span class="n">valores</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Variável: valores | Tipo: {type(valores)} | Valor: </span><span class="si">{valores}</span><span class="s1">'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Variável: retorno | Tipo: {type(retorno)} | Valor: </span><span class="si">{retorno}</span><span class="s1">'</span><span class="p">)</span>
</pre></div>
<p>Resultado da execução:</p>
<div class="highlight"><pre><span></span><span class="err">Variável: valores | Tipo: <class 'list'> | Valor: [5, 2, 8, 6, 4]</span>
<span class="err">Variável: retorno | Tipo: <class 'list'> | Valor: [10, 4, 16, 12, 8]</span>
</pre></div>
<p>Com essa execução é possível observar que a variável <code>valores</code> continua com os valores que tinha antes da execução da função, e a variável retorno apresenta uma lista com os dobros, ou seja, a função não altera a lista passada como argumento e retorna uma nova lista com os valores calculados. Observado a assinatura da função, ela recebe uma lista de inteiros e retorna uma lista de inteiros. Isso permite chamar essa função diretamente nos argumentos para outra função, como <code>print(dobro_copia(valores))</code>, nesse caso a função <code>print</code> receberia a lista de dobros como argumento. Porém caso o retorno da função não seja armazenado, parecerá que a função não fez nada, ou não funcionou. Então em alguns casos, quando o valor anterior não é mais necessário, pode-se reatribuir o retorno da função a própria variável passada como argumento:</p>
<div class="highlight"><pre><span></span><span class="n">valores</span> <span class="o">=</span> <span class="n">dobro_copia</span><span class="p">(</span><span class="n">valores</span><span class="p">)</span>
</pre></div>
<h3>Função híbrida</h3>
<p>Ainda é possível mesclar os dois padrões de função, alterando o valor passado e retornando-o. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">typing</span> <span class="kn">import</span> <span class="n">List</span>
<span class="k">def</span> <span class="nf">dobro_hibrido</span><span class="p">(</span><span class="n">lista</span><span class="p">:</span> <span class="n">List</span><span class="p">[</span><span class="nb">int</span><span class="p">])</span> <span class="o">-></span> <span class="n">List</span><span class="p">[</span><span class="nb">int</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="nb">len</span><span class="p">(</span><span class="n">lista</span><span class="p">)):</span>
<span class="n">lista</span><span class="p">[</span><span class="n">i</span><span class="p">]</span> <span class="o">=</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">lista</span><span class="p">[</span><span class="n">i</span><span class="p">]</span>
<span class="k">return</span> <span class="n">lista</span>
<span class="n">valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">retorno</span> <span class="o">=</span> <span class="n">dobro_hibrido</span><span class="p">(</span><span class="n">valores</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Variável: valores | Tipo: {type(valores)} | Valor: </span><span class="si">{valores}</span><span class="s1">'</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="sa">f</span><span class="s1">'Variável: retorno | Tipo: {type(retorno)} | Valor: </span><span class="si">{retorno}</span><span class="s1">'</span><span class="p">)</span>
</pre></div>
<p>Resultado da execução:</p>
<div class="highlight"><pre><span></span><span class="err">Variável: valores | Tipo: <class 'list'> | Valor: [10, 4, 16, 12, 8]</span>
<span class="err">Variável: retorno | Tipo: <class 'list'> | Valor: [10, 4, 16, 12, 8]</span>
</pre></div>
<p>Nesse caso, pode-se apenas chamar a função, como também utilizá-la nos argumentos de outras funções. Porém para se ter os valores originais, deve-se fazer uma cópia manualmente antes de executar a função.</p>
<h2>Exemplo na biblioteca padrão</h2>
<p>Na biblioteca padrão do Python, existem os métodos <code>sort</code> e <code>reverse</code> que seguem o padrão <em>in place</em>, e as funções <code>sorted</code> e <code>reversed</code> que seguem o padrão cópia de valor, podendo ser utilizados para ordenar e inverter os valores de uma lista, por exemplo. Quando não é mais necessário uma cópia da lista com a ordem original, é preferível utilizar funções <em>in place</em>, que alteram a própria lista, e como não criam uma cópia da lista, utilizam menos memória. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="n">valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">valores</span><span class="o">.</span><span class="n">sort</span><span class="p">()</span>
<span class="n">valores</span><span class="o">.</span><span class="n">reverse</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">valores</span><span class="p">)</span>
</pre></div>
<p>Se for necessário manter uma cópia da lista inalterada, deve-se optar pelas funções de cópia de valor. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="n">valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">novos_valores</span> <span class="o">=</span> <span class="nb">reversed</span><span class="p">(</span><span class="nb">sorted</span><span class="p">(</span><span class="n">valores</span><span class="p">))</span>
<span class="nb">print</span><span class="p">(</span><span class="n">novos_valores</span><span class="p">)</span>
</pre></div>
<p>Porém esse exemplo cria duas cópias da lista, uma em cada função. Para criar apenas uma cópia, pode-se misturar funções <em>in place</em> com cópia de valor. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="n">valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">novos_valores</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">valores</span><span class="p">)</span>
<span class="n">novos_valores</span><span class="o">.</span><span class="n">reverse</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">novos_valores</span><span class="p">)</span>
</pre></div>
<p>Também vale observar que algumas utilizações dessas funções podem dar a impressão de que elas não funcionaram, como:</p>
<div class="highlight"><pre><span></span><span class="n">valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="nb">sorted</span><span class="p">(</span><span class="n">valores</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">valores</span><span class="p">)</span> <span class="c1"># Imprime a lista original, e não a ordenada</span>
<span class="nb">print</span><span class="p">(</span><span class="n">valores</span><span class="o">.</span><span class="n">sort</span><span class="p">())</span> <span class="c1"># Imprime None e não a lista</span>
</pre></div>
<h2>Considerações</h2>
<p>Nem sempre é possível utilizar o padrão desejado, <em>strings</em> no Python (<code>str</code>) são imutáveis, logo todas as funções que manipulam elas seguiram o padrão cópia de valor, e para outros tipos, pode ocorrer de só existir funções <em>in place</em>, sendo necessário fazer uma cópia manualmente antes de chamar a função, caso necessário. Para saber qual padrão a função implementa, é necessário consultar sua documentação, ou verificando sua assinatura, embora ainda possa existir uma dúvida entre cópia de valor e híbrida, visto que a assinatura dos dois padrões são iguais.</p>
<p>Os exemplos aqui dados são didáticos. Caso deseja-se ordenar de forma reversa, tanto o método <code>sort</code>, quanto a função <code>sorted</code> podem receber como argumento <code>reverse=True</code>, e assim já fazer a ordenação reversa. Assim como é possível criar uma nova lista já com os valores, sem precisar adicionar manualmente item por item, como os exemplos:</p>
<div class="highlight"><pre><span></span><span class="n">valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">5</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">4</span><span class="p">]</span>
<span class="n">partes_dos_valores</span> <span class="o">=</span> <span class="n">valores</span><span class="p">[</span><span class="mi">2</span><span class="p">:]</span>
<span class="n">novos_valores</span> <span class="o">=</span> <span class="p">[</span><span class="mi">2</span> <span class="o">*</span> <span class="n">valor</span> <span class="k">for</span> <span class="n">valor</span> <span class="ow">in</span> <span class="n">valores</span><span class="p">]</span>
</pre></div>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2021-03-29T15:00:00+00:00
Eduardo Klosowski
-
PythonClub: Encapsulamento da lógica do algoritmo
https://pythonclub.com.br/encapsulamento-da-logica-do-algoritmo.html
<p>Muitas listas de exercícios de lógica de programação pedem em algum momento que um valor seja lido do teclado, e caso esse valor seja inválido, deve-se avisar, e repetir a leitura até que um valor válido seja informado. Utilizando a ideia de <a href="https://dev.to/acaverna/otimizando-o-algoritmo-passo-a-passo-4co0">otimização do algoritmo passo a passo</a>, começando com uma solução simples, pretendo estudar como reduzir a duplicação de código alterando o algoritmo, encapsulando a lógica em funções, e encapsulando em classes.</p>
<h2>Exercício</h2>
<p>Um exemplo de exercício que pede esse tipo de validação é a leitura de notas, que devem estar entre 0 e 10. A solução mais simples, consiste em ler um valor, e enquanto esse valor for inválido, dar o aviso e ler outro valor. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="n">nota</span> <span class="o">=</span> <span class="nb">float</span><span class="p">(</span><span class="nb">input</span><span class="p">(</span><span class="s1">'Digite a nota: '</span><span class="p">))</span>
<span class="k">while</span> <span class="n">nota</span> <span class="o"><</span> <span class="mi">0</span> <span class="ow">or</span> <span class="n">nota</span> <span class="o">></span> <span class="mi">10</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Nota inválida'</span><span class="p">)</span>
<span class="n">nota</span> <span class="o">=</span> <span class="nb">float</span><span class="p">(</span><span class="nb">input</span><span class="p">(</span><span class="s1">'Digite a nota: '</span><span class="p">))</span>
</pre></div>
<p>Esse algoritmo funciona, porém existe uma duplicação no código que faz a leitura da nota (uma antes do <em>loop</em> e outra dentro). Caso seja necessário uma alteração, como a mudança da nota para um valor inteiro entre 0 e 100, deve-se alterar os dois lugares, e se feito em apenas um lugar, o algoritmo poderia processar valores inválidos.</p>
<h3>Alterando o algoritmo</h3>
<p>Visando remover a repetição de código, é possível unificar a leitura do valor dentro do <em>loop</em>, uma vez que é necessário repetir essa instrução até que o valor válido seja obtido. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">nota</span> <span class="o">=</span> <span class="nb">float</span><span class="p">(</span><span class="nb">input</span><span class="p">(</span><span class="s1">'Digite a nota: '</span><span class="p">))</span>
<span class="k">if</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">nota</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="s1">'Nota inválida!'</span><span class="p">)</span>
</pre></div>
<p>Dessa forma, não existe mais a repetição de código. A condição de parada, que antes verificava se o valor era inválido (o que pode ter uma leitura não tão intuitiva), agora verifica se é um valor válido (que é geralmente é mais fácil de ler e escrever a condição). E a ordem dos comandos dentro do <em>loop</em>, que agora estão em uma ordem que facilita a leitura, visto que no algoritmo anterior era necessário tem em mente o que era executado antes do <em>loop</em>.</p>
<p>Porém esses algoritmos validam apenas o valor lido, apresentando erro caso seja informado um valor com formato inválido, como letras em vez de números. Isso pode ser resolvido tratando as exceções lançadas. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">nota</span> <span class="o">=</span> <span class="nb">float</span><span class="p">(</span><span class="nb">input</span><span class="p">(</span><span class="s1">'Digite a nota: '</span><span class="p">))</span>
<span class="k">if</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">nota</span> <span class="o"><=</span> <span class="mi">10</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="o">...</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Nota inválida!'</span><span class="p">)</span>
</pre></div>
<h3>Encapsulamento da lógica em função</h3>
<p>Caso fosse necessário ler várias notas, com os algoritmos apresentados até então, seria necessário repetir todo esse trecho de código, ou utilizá-lo dentro de uma estrutura de repetição. Para facilitar sua reutilização, evitando a duplicação de código, é possível encapsular esse algoritmo dentro de uma função. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">nota_input</span><span class="p">(</span><span class="n">prompt</span><span class="p">):</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">nota</span> <span class="o">=</span> <span class="nb">float</span><span class="p">(</span><span class="nb">input</span><span class="p">(</span><span class="n">prompt</span><span class="p">))</span>
<span class="k">if</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">nota</span> <span class="o"><=</span> <span class="mi">10</span><span class="p">:</span>
<span class="k">break</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="o">...</span>
<span class="nb">print</span><span class="p">(</span><span class="s1">'Nota inválida!'</span><span class="p">)</span>
<span class="k">return</span> <span class="n">nota</span>
<span class="n">nota1</span> <span class="o">=</span> <span class="n">nota_input</span><span class="p">(</span><span class="s1">'Digite a primeira nota: '</span><span class="p">)</span>
<span class="n">nota2</span> <span class="o">=</span> <span class="n">nota_input</span><span class="p">(</span><span class="s1">'Digite a segunda nota: '</span><span class="p">)</span>
</pre></div>
<h3>Encapsulamento da lógica em classes</h3>
<p>Em vez de encapsular essa lógica em uma função, é possível encapsulá-la em uma classe, o que permitiria separar cada etapa do algoritmo em métodos, assim como ter um método responsável por controlar qual etapa deveria ser chamada em qual momento. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ValidaNotaInput</span><span class="p">:</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Nota inválida!'</span>
<span class="k">def</span> <span class="nf">ler_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">input</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entrada</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="n">entrada</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">validar_nota</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">nota</span><span class="p">):</span>
<span class="k">return</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">nota</span> <span class="o"><=</span> <span class="mi">10</span>
<span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">nota</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ler_entrada</span><span class="p">(</span><span class="n">prompt</span><span class="p">))</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validar_nota</span><span class="p">(</span><span class="n">nota</span><span class="p">):</span>
<span class="k">break</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="o">...</span>
<span class="nb">print</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">mensagem_valor_invalido</span><span class="p">)</span>
<span class="k">return</span> <span class="n">nota</span>
<span class="n">nota_input</span> <span class="o">=</span> <span class="n">ValidaNotaInput</span><span class="p">()</span>
<span class="n">nota</span> <span class="o">=</span> <span class="n">nota_input</span><span class="p">(</span><span class="s1">'Digite a nota: '</span><span class="p">)</span>
</pre></div>
<p>Vale observar que o método <code>__call__</code> permite que o objeto criado a partir dessa classe seja chamado como se fosse uma função. Nesse caso ele é o responsável por chamar cada etapa do algoritmo, como: <code>ler_entrada</code> que é responsável por ler o que foi digitado no teclado, <code>transformar_entrada</code> que é responsável por converter o texto lido para o tipo desejado (converter de <code>str</code> para <code>float</code>), e <code>validar_nota</code> que é responsável por dizer se o valor é válido ou não. Vale observar que ao dividir o algoritmo em métodos diferentes, seu código principal virou uma espécie de código comentado, descrevendo o que está sendo feito e onde está sendo feito.</p>
<p>Outra vantagem de encapsular a lógica em classe, em vez de uma função, é a possibilidade de generalizá-la. Se fosse necessário validar outro tipo de entrada, encapsulando em uma função, seria necessário criar outra função repetindo todo o algoritmo, alterando apenas a parte referente a transformação do valor lido, e validação, o que gera uma espécie de repetição de código. Ao encapsular em classes, é possível se aproveitar dos mecanismos de herança para evitar essa repetição. Exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">ValidaInput</span><span class="p">:</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Valor inválido!'</span>
<span class="k">def</span> <span class="nf">ler_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">input</span><span class="p">(</span><span class="n">prompt</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entrada</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">NotImplementedError</span>
<span class="k">def</span> <span class="fm">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">prompt</span><span class="p">):</span>
<span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">valor</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">ler_entrada</span><span class="p">(</span><span class="n">prompt</span><span class="p">))</span>
<span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">validar_valor</span><span class="p">(</span><span class="n">valor</span><span class="p">):</span>
<span class="k">break</span>
<span class="k">except</span> <span class="ne">ValueError</span><span class="p">:</span>
<span class="o">...</span>
<span class="nb">print</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">mensagem_valor_invalido</span><span class="p">)</span>
<span class="k">return</span> <span class="n">valor</span>
<span class="k">class</span> <span class="nc">ValidaNomeInput</span><span class="p">(</span><span class="n">ValidaInput</span><span class="p">):</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Nome inválido!'</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entrada</span><span class="p">):</span>
<span class="k">return</span> <span class="n">entrada</span><span class="o">.</span><span class="n">strip</span><span class="p">()</span><span class="o">.</span><span class="n">title</span><span class="p">()</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="k">return</span> <span class="n">valor</span> <span class="o">!=</span> <span class="s1">''</span>
<span class="k">class</span> <span class="nc">ValidaNotaInput</span><span class="p">(</span><span class="n">ValidaInput</span><span class="p">):</span>
<span class="n">mensagem_valor_invalido</span> <span class="o">=</span> <span class="s1">'Nota inválida!'</span>
<span class="k">def</span> <span class="nf">transformar_entrada</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">entrada</span><span class="p">):</span>
<span class="k">return</span> <span class="nb">float</span><span class="p">(</span><span class="n">entrada</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">validar_valor</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="k">return</span> <span class="mi">0</span> <span class="o"><=</span> <span class="n">valor</span> <span class="o"><=</span> <span class="mi">10</span>
<span class="n">nome_input</span> <span class="o">=</span> <span class="n">ValidaNomeInput</span><span class="p">()</span>
<span class="n">nota_input</span> <span class="o">=</span> <span class="n">ValidaNotaInput</span><span class="p">()</span>
<span class="n">nome</span> <span class="o">=</span> <span class="n">nome_input</span><span class="p">(</span><span class="s1">'Digite o nome: '</span><span class="p">)</span>
<span class="n">nota</span> <span class="o">=</span> <span class="n">nota_input</span><span class="p">(</span><span class="s1">'Digite a nota: '</span><span class="p">)</span>
</pre></div>
<p>Dessa forma, é possível reutilizar o código já existente para criar outras validações, sendo necessário implementar apenas como converter a <code>str</code> lida do teclado para o tipo desejado, e como esse valor deve ser validado. Não é necessário entender e repetir a lógica de ler o valor, validá-lo, imprimir a mensagem de erro, e repetir até que seja informado um valor válido.</p>
<h2>Considerações</h2>
<p>É possível encapsular a lógica de um algoritmo em funções ou em classes. Embora para fazê-lo em uma classe exija conhecimentos de programação orientada a objetos, o seu reaproveitamento é facilitado, abstraindo toda a complexidade do algoritmo, que pode ser disponibilizado através de uma biblioteca, exigindo apenas a implementações de métodos simples por quem for a utilizar.</p>
<p>Ainda poderia ser discutido outras formas de fazer essa implementação, como passar funções como parâmetro e a utilização de <a href="https://docs.python.org/pt-br/3/library/asyncio-task.html">corrotinas</a> no encapsulamento do algoritmo em função, assim como a utilização de <a href="https://docs.python.org/pt-br/3/library/functions.html#classmethod">classmethod</a>, <a href="https://docs.python.org/pt-br/3/library/functions.html#staticmethod">staticmethod</a> e <a href="https://docs.python.org/pt-br/3/library/abc.html">ABC</a> no encapsulamento do algoritmo em classes.</p>
<hr />
<p>Esse artigo foi publicado originalmente no <a href="https://eduardoklosowski.github.io/blog/">meu blog</a>, passe por lá, ou siga-me no <a href="https://dev.to/eduardoklosowski">DEV</a> para ver mais artigos que eu escrevi.</p>
2021-03-02T18:00:00+00:00
Eduardo Klosowski
-
PythonClub: Fazendo backup do banco de dados no Django
https://pythonclub.com.br/fazendo-backup-do-banco-de-dados-no-django.html
<h2>Apresentação</h2>
<p>Em algum momento, durante o seu processo de desenvolvimento com Django, pode ser que surja a necessidade de criar e restaurar o banco de dados da aplicação. Pensando nisso, resolvi fazer um pequeno tutorial, básico, de como realizar essa operação.</p>
<p>Nesse tutorial, usaremos o <a href="https://github.com/django-dbbackup/django-dbbackup">django-dbbackup</a>, um pacote desenvolvido especificamente para isso.</p>
<h2>Configurando nosso ambiente</h2>
<p>Primeiro, partindo do início, vamos criar uma pasta para o nosso projeto e, nela, isolar o nosso ambiente de desenvolvimento usando uma <a href="https://virtualenv.pypa.io/en/latest/index.html">virtualenv</a>:</p>
<div class="highlight"><pre><span></span>mkdir projeto_db <span class="o">&&</span> <span class="nb">cd</span> projeto_db <span class="c1">#criando a pasta do nosso projeto</span>
virtualenv -p python3.8 env <span class="o">&&</span> <span class="nb">source</span> env/bin/activate <span class="c1">#criando e ativando a nossa virtualenv</span>
</pre></div>
<p>Depois disso e com o nosso ambiente já ativo, vamos realizar os seguintes procedimentos:</p>
<div class="highlight"><pre><span></span>pip install -U pip <span class="c1">#com isso, atualizamos a verão do pip instalado</span>
</pre></div>
<h2>Instalando as dependências</h2>
<p>Agora, vamos instalar o <a href="https://www.djangoproject.com/">Django</a> e o pacote que usaremos para fazer nossos backups.</p>
<div class="highlight"><pre><span></span>pip install <span class="nv">Django</span><span class="o">==</span><span class="m">3</span>.1.2 <span class="c1">#instalando o Django</span>
pip install django-dbbackup <span class="c1">#instalando o django-dbbackup</span>
</pre></div>
<h2>Criando e configurando projeto</h2>
<p>Depois de instaladas nossas dependências, vamos criar o nosso projeto e configurar o nosso pacote nas configurações do Django.</p>
<div class="highlight"><pre><span></span>django-admin startproject django_db . <span class="c1">#dentro da nossa pasta projeto_db, criamos um projeto Django com o nome de django_db.</span>
</pre></div>
<p>Depois de criado nosso projeto, vamos criar e popular o nosso banco de dados.</p>
<div class="highlight"><pre><span></span>python manage.py migrate <span class="c1">#com isso, sincronizamos o estado do banco de dados com o conjunto atual de modelos e migrações.</span>
</pre></div>
<p>Criado nosso banco de dados, vamos criar um superusuário para podemos o painel admin do nosso projeto.</p>
<div class="highlight"><pre><span></span>python manage.py createsuperuser
</pre></div>
<p>Perfeito. Já temos tudo que precisamos para executar nosso projeto. Para execução dele, é só fazermos:</p>
<div class="highlight"><pre><span></span>python manage.py runserver
</pre></div>
<p>Você terá uma imagem assim do seu projeto:</p>
<p><img alt="" src="https://jacksonosvaldo.github.io/img/django_db.png" /></p>
<h2>Configurando o django-dbbackup</h2>
<p>Dentro do seu projeto, vamos acessar o arquivo settings.py, como expresso abaixo:</p>
<div class="highlight"><pre><span></span>django_db/
├── settings.py
</pre></div>
<p>Dentro desse arquivos iremos, primeiro, adiconar o django-dbbackup às apps do projeto:</p>
<div class="highlight"><pre><span></span><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">(</span>
<span class="o">...</span>
<span class="s1">'dbbackup'</span><span class="p">,</span> <span class="c1"># adicionando django-dbbackup</span>
<span class="p">)</span>
</pre></div>
<p>Depois de adicionado às apps, vamos dizer para o Django o que vamos salvar no backup e, depois, indicar a pasta para onde será encaminhado esse arquivo. Essa inserção deve ou pode ser feita no final do arquivo <em>settings.py</em>:</p>
<div class="highlight"><pre><span></span><span class="n">DBBACKUP_STORAGE</span> <span class="o">=</span> <span class="s1">'django.core.files.storage.FileSystemStorage'</span> <span class="c1">#o que salvar</span>
<span class="n">DBBACKUP_STORAGE_OPTIONS</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'location'</span><span class="p">:</span> <span class="s1">'backups/'</span><span class="p">}</span> <span class="c1"># onde salvar</span>
</pre></div>
<p>Percebam que dissemos para o Django salvar o backup na pasta <em>backups</em>, mas essa pasta ainda não existe no nosso projeto. Por isso, precisamos criá-la [fora da pasta do projeto]:</p>
<div class="highlight"><pre><span></span>mkdir backups
</pre></div>
<h2>Criando e restaurando nosso backup</h2>
<p>Já temos tudo pronto. Agora, vamos criar o nosso primeiro backup:</p>
<div class="highlight"><pre><span></span>python manage.py dbbackup
</pre></div>
<p>Depois de exetudado, será criado um arquivo -- no nosso exemplo, esse arquivo terá uma extensão .dump --, salvo na pasta <em>backups</em>. Esse arquivo contem todo backup do nosso banco de dados.</p>
<p>Para recuperarmos nosso banco, vamos supor que migramos nosso sistema de um servidor antigo para um novo e, por algum motivo, nossa base de dados foi corrompida, inviabilizando seu uso. Ou seja, estamos com o sistema/projeto sem banco de dados -- ou seja, exlua ou mova a a sua base dados .sqlite3 para que esse exemplo seja útil --, mas temos os backups. Com isso, vamos restaurar o banco:</p>
<div class="highlight"><pre><span></span>python manage.py dbrestore
</pre></div>
<p>Prontinho, restauramos nosso banco de dados. O interessante do django-dbbackup, dentre outras coisas, é que ele gera os backups com datas e horários específicos, facilitando o processo de recuperação das informações mais recentes.</p>
<p>Por hoje é isso, pessoal. Até a próxima. ;) </p>
2020-10-30T13:40:00+00:00
Jackson Osvaldo
-
Filipe Saraiva: Seqtembro de eventos virtuais e gratuitos sobre Qt e KDE
https://blog.filipesaraiva.info/?p=2185
<p>(Ok a piada com <em>seqtembro</em> funciona melhor na versão em inglês, <em>seqtember</em>, mas simbora)</p>
<p>Por uma grande coincidência, obra do destino, ou nada disso, teremos um Setembro de 2020 repleto de eventos virtuais e gratuitos de alta qualidade sobre <a href="https://www.qt.io/" target="_blank" rel="noopener noreferrer">Qt</a> e <a href="https://kde.org/" target="_blank" rel="noopener noreferrer">KDE</a>.</p>
<p>Começando de 4 à 11 do referido mês teremos o <a href="https://akademy.kde.org/2020/" target="_blank" rel="noopener noreferrer">Akademy 2020</a>, o grande encontro mundial da comunidade KDE que esse ano, por motivos que todos sabemos, acontecerá de forma virtual. A <a href="https://akademy.kde.org/2020/program" target="_blank" rel="noopener noreferrer">programação</a> do Akademy traz palestras, treinamentos, <em>hacking sessions</em>, discussões com foco em aplicações KDE específicas, e mais, reunindo hackers, designers, gerentes de projetos, tradutores, e colabores dos mais diversos segmentos para discutir e planejar o KDE e seus futuros passos.</p>
<p><a href="https://blog.filipesaraiva.info/?attachment_id=2190" rel="attachment wp-att-2190"><img /></a></p>
<p>E como falamos em KDE, por extensão, também falamos em Qt – afinal, grande parte das aplicações é escrita nesse framework. Portanto, mesmo que você trabalhe com Qt mas não use nada do KDE, vale a pena participar do evento – e também se perguntar “porque diabos não estou usando e desenvolvendo aplicações do KDE?”.</p>
<p>Um incentivo extra é que durante o Akademy, entre 7 e 11, acontecerá o <a href="https://www.qtdesktopdays.com/" target="_blank" rel="noopener noreferrer">Qt Desktop Days</a>, evento da <a href="https://www.kdab.com/" target="_blank" rel="noopener noreferrer">KDAB</a> voltado para Qt no desktop (surpresa?). A <a href="https://www.qtdesktopdays.com/program/" target="_blank" rel="noopener noreferrer">programação preliminar</a> já está disponível e será muito interessante ver os avanços da tecnologia em um campo que pode parecer menos <em>sexy</em> hoje em dia, por conta da muita atenção dada a projetos mobile ou embarcados, mas que pelo contrário, continua vibrante e recebendo muito investimento.</p>
<p><img /></p>
<p>Após uma rápida pausa para um respiro, temos a <a href="https://bit.do/maratonaqt" target="_blank" rel="noopener noreferrer">Maratona Qt</a>. Nosso amigo <a href="https://sandroandrade.org/" target="_blank" rel="noopener noreferrer">Sandro Andrade</a>, professor do IFBA e colaborador de longa data do KDE, resolveu dedicar uma semana inteira, de 14 à 18 de setembro, para apresentar 5 tópicos sobre o Qt tratando de seus fundamentos e passos iniciais de cada um. O programa cobre QML, C++ e Qt, Qt no Android, no iOS, na web (sim!), computação gráfica e mesmo jogos! Extremamente recomendada pra todo mundo que conhece ou quer conhecer o framework.</p>
<p>A Maratona Qt vai servir como um esquenta para a <a href="https://br.qtcon.org/" target="_blank" rel="noopener noreferrer">QtCon Brasil 2020</a>, esse ano também virtual. Em 26 e 27 de setembro o pessoal da <a href="https://qmob.solutions/" target="_blank" rel="noopener noreferrer">qmob.solutions</a> reunirá desenvolvedores Qt de vários países para apresentarem, entre outras coisas, trabalhos com Wayland, visão computacional e IA, análise de dados, Python, containers, prototipagem, embarcados, e outros, tudo envolvendo Qt! E também haverá uma apresentação sobre a próxima versão <em>major</em> da ferramenta, Qt 6.</p>
<p>Portanto pessoal, reservem este mês para uma grande imersão nos vários aspectos e possibilidades disponibilizadas pelo Qt.</p>
2020-08-29T18:48:00+00:00
-
Gabbleblotchits: MinHashing all the things: a quick analysis of MAG search results
https://blog.luizirber.org/2020/07/24/mag-results/
<p><a href="https://blog.luizirber.org/2020/07/22/mag-search/">Last time</a> I described a way to search MAGs in metagenomes,
and teased about interesting results.
Let's dig in some of them!</p>
<p>I prepared a <a href="https://github.com/luizirber/2020-07-22-mag-search/">repo</a> with the data and a notebook with the analysis I did in this
post.
You can also follow along in <a href="https://mybinder.org">Binder</a>,
as well as do your own analysis! <a href="https://mybinder.org/v2/gh/luizirber/2020-07-22-mag-search/master?filepath=index.ipynb"><img alt="Binder" src="https://mybinder.org/badge_logo.svg" /></a></p>
<h2>Preparing some metadata</h2>
<p>The supplemental materials for <a href="https://www.nature.com/articles/sdata2017203">Tully et al</a> include more details about each MAG,
so let's download them.
I prepared a small snakemake workflow to do that,
as well as downloading information about the SRA datasets from Tara Oceans
(the dataset used to generate the MAGs),
as well as from <a href="https://www.nature.com/articles/s41564-017-0012-7">Parks et al</a>,
which also generated MAGs from Tara Oceans.
Feel free to include them in your analysis,
but I was curious to find matches in other metagenomes.</p>
<h2>Loading the data</h2>
<p>The results from the MAG search are in a CSV file,
with a column for the MAG name,
another for the SRA dataset ID for the metagenome and a third column for the
containment of the MAG in the metagenome.
I also fixed the names to make it easier to query,
and finally removed the Tara and Parks metagenomes
(because we already knew they contained these MAGs).</p>
<p>This left us with 23,644 SRA metagenomes with matches,
covering 2,291 of the 2,631 MAGs.
These are results for a fairly low containment (10%),
so if we limit to MAGs with more than 50% containment we still have 1,407 MAGs and 2,938 metagenomes left.</p>
<h2>TOBG_NP-110, I choose you!</h2>
<p>That's still a lot,
so I decided to pick a candidate to check before doing any large scale analysis.
I chose TOBG_NP-110 because there were many matches above 50% containment,
and even some at 99%.
Turns out it is also an Archaeal MAG that failed to be classified further than Phylum level (Euryarchaeota),
with a 70.3% complete score in the original analysis.
Oh, let me dissect the name a bit:
TOBG is "Tara Ocean Binned Genome" and "NP" is North Pacific.</p>
<p>And so I went checking where the other metagenome matches came from.
5 of the 12 matches above 50% containment come from one study,
<a href="https://trace.ncbi.nlm.nih.gov/Traces/sra/?study=SRP044185">SRP044185</a>,
with samples collected from a column of water in a station in Manzanillo, Mexico.
Other 3 matches come from
<a href="https://trace.ncbi.nlm.nih.gov/Traces/sra/?study=SRP003331">SRP003331</a>,
in the South Pacific ocean (in northern Chile).
Another match,
<a href="https://trace.ncbi.nlm.nih.gov/Traces/sra/?run=ERR3256923">ERR3256923</a>,
also comes from the South Pacific.</p>
<h2>What else can I do?</h2>
<p>I'm curious to follow <a href="https://merenlab.org/data/refining-mags/">the refining MAGs</a> tutorial from the Meren Lab and see where this goes,
and especially in using <a href="https://genomebiology.biomedcentral.com/articles/10.1186/s13059-020-02066-4"><code>spacegraphcats</code></a>
to extract neighborhoods from the MAG and better evaluate what is missing or if there are other interesting bits that
the MAG generation methods ended up discarding.</p>
<p>So, for now that's it.
But more important,
I didn't want to sit on these results until there is a publication in press,
especially when there are people that can do so much more with these,
so I decided to make it all public.
It is way more exciting to see this being used to know more about these
organisms than me being the only one with access to this info.</p>
<p>And yesterday I saw <a href="https://twitter.com/DrJonathanRosa/status/1286381346605027328">this tweet</a> by
<a href="https://twitter.com/DrJonathanRosa/status/1286381346605027328">@DrJonathanRosa</a>,
saying:</p>
<blockquote>
<p>I don’t know who told students that the goal of research is to find some
previously undiscovered research topic, claim individual ownership over it,
& fiercely protect it from theft, but that almost sounds like, well,
colonialism, capitalism, & policing </p>
</blockquote>
<p>Amen.</p>
<h2>I want to run this with my data!</h2>
<p>Next time. But we will have a discussion about scientific infrastructure and
sustainability first =]</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://twitter.com/luizirber/status/1286700888111738880">Thread on Twitter</a></li>
</ul>
2020-07-24T15:00:00+00:00
luizirber
-
Gabbleblotchits: MinHashing all the things: searching for MAGs in the SRA
https://blog.luizirber.org/2020/07/22/mag-search/
<p>(or: Top-down and bottom-up approaches for working around sourmash limitations)</p>
<p>In the last month I updated <a href="https://wort.oxli.org">wort</a>,
the system I developed for computing sourmash signature for public genomic databases,
and started calculating signatures
for the <a href="https://www.ncbi.nlm.nih.gov/sra/?term=%22METAGENOMIC%22%5Bsource%5D+NOT+amplicon%5BAll+Fields%5D)">metagenomes</a> in the <a href="https://www.ncbi.nlm.nih.gov/sra/">Sequence Read Archive</a>.
This is a more challenging subset than the <a href="https://blog.luizirber.org/2016/12/28/soursigs-arch-1/">microbial datasets</a> I was doing previously,
since there are around 534k datasets from metagenomic sources in the SRA,
totalling 447 TB of data.
Another problem is the size of the datasets,
ranging from a couple of MB to 170 GB.
Turns out that the workers I have in <code>wort</code> are very good for small-ish datasets,
but I still need to figure out how to pull large datasets faster from the SRA,
because the large ones take forever to process...</p>
<p>The good news is that I managed to calculate signatures for almost 402k of them
<sup id="sf-mag-search-1-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-mag-search-1" class="simple-footnote" title="pulling about a 100 TB in 3 days, which was pretty fun to see because I ended up DDoS myself because I couldn't download the generated sigs fast enough from the S3 bucket where they are temporarily stored =P">1</a></sup>,
which already let us work on some pretty exciting problems =]</p>
<h2>Looking for MAGs in the SRA</h2>
<p>Metagenome-assembled genomes are essential for studying organisms that are hard to isolate and culture in lab,
especially for environmental metagenomes.
<a href="https://www.nature.com/articles/sdata2017203">Tully et al</a> published 2,631 draft MAGs from 234 samples collected during the Tara Oceans expedition,
and I wanted to check if they can also be found in other metagenomes besides the Tara Oceans ones.
The idea is to extract the reads from these other matches and evaluate how the MAG can be improved,
or at least evaluate what is missing in them.
I choose to use environmental samples under the assumption they are easier to deposit on the SRA and have public access,
but there are many human gut microbiomes in the SRA and this MAG search would work just fine with those too.</p>
<p>Moreover,
I want to search for containment,
and not similarity.
The distinction is subtle,
but similarity takes into account both datasets sizes
(well, the size of the union of all elements in both datasets),
while containment only considers the size of the query.
This is relevant because the similarity of a MAG and a metagenome is going to be very small (and is symmetrical),
but the containment of the MAG in the metagenome might be large
(and is asymmetrical, since the containment of the metagenome in the MAG is likely very small because the metagenome is so much larger than the MAG).</p>
<h2>The computational challenge: indexing and searching</h2>
<p>sourmash signatures are a small fraction of the original size of the datasets,
but when you have hundreds of thousands of them the collection ends up being pretty large too.
More precisely, 825 GB large.
That is way bigger than any index I ever built for sourmash,
and it would also have pretty distinct characteristics than what we usually do:
we tend to index genomes and run <code>search</code> (to find similar genomes) or <code>gather</code>
(to decompose metagenomes into their constituent genomes),
but for this MAG search I want to find which metagenomes have my MAG query above a certain containment threshold.
Sort of a <code>sourmash search --containment</code>,
but over thousands of metagenome signatures.
The main benefit of an SBT index in this context is to avoid checking all signatures because we can prune the search early,
but currently SBT indices need to be totally loaded in memory during <code>sourmash index</code>.
I will have to do this in the medium term,
but I want a solution NOW! =]</p>
<p><a href="https://github.com/dib-lab/sourmash/releases/tag/v3.4.0">sourmash 3.4.0</a> introduced <code>--from-file</code> in many commands,
and since I can't build an index I decided to use it to load signatures for the metagenomes.
But... <code>sourmash search</code> tries to load all signatures in memory,
and while I might be able to find a cluster machine with hundreds of GBs of RAM available,
that's not very practical.</p>
<p>So, what to do?</p>
<h2>The top-down solution: a snakemake workflow</h2>
<p>I don't want to modify sourmash now,
so why not make a workflow and use snakemake to run one <code>sourmash search --containment</code> for each metagenome?
That means 402k tasks,
but at least I can use <a href="https://snakemake.readthedocs.io/en/stable/executing/cli.html#dealing-with-very-large-workflows">batches</a> and <a href="https://slurm.schedmd.com/job_array.html">SLURM job arrays</a> to submit reasonably-sized jobs to our HPC queue.
After running all batches I summarized results for each task,
and it worked well for a proof of concept.</p>
<p>But... it was still pretty resource intensive:
each task was running one query MAG against one metagenome,
and so each task needed to do all the overhead of starting the Python interpreter and parsing the query signature,
which is exactly the same for all tasks.
Extending it to support multiple queries to the same metagenome would involve duplicating tasks,
and 402k metagenomes times 2,631 MAGs is...
a very large number of jobs.</p>
<p>I also wanted to avoid clogging the job queues,
which is not very nice to the other researchers using the cluster.
This limited how many batches I could run in parallel...</p>
<h2>The bottom-up solution: Rust to the rescue!</h2>
<p>Thinking a bit more about the problem,
here is another solution:
what if we load all the MAGs in memory
(as they will be queried frequently and are not that large),
and then for each metagenome signature load it,
perform all MAG queries,
and then unload the metagenome signature from memory?
This way we can control memory consumption
(it's going to be proportional to all the MAG sizes plus the size of the largest metagenome)
and can also efficiently parallelize the code because each task/metagenome is independent
and the MAG signatures can be shared freely (since they are read-only).</p>
<p>This could be done with the sourmash Python API plus <code>multiprocessing</code> or some
other parallelization approach (maybe dask?),
but turns out that everything we need comes from the Rust API.
Why not enjoy a bit of the <a href="https://doc.rust-lang.org/stable/book/ch16-00-concurrency.html">fearless concurrency</a> that is one of the major Rust goals?</p>
<p><a href="https://github.com/luizirber/phd/blob/aa1ed9eb33ba71fdf9b3f2c92931701be6df00cd/experiments/wort/sra_search/src/main.rs">The whole code</a> ended up being 176 lines long,
including command line parsing using <a href="https://docs.rs/structopt/latest/structopt/">strucopt</a> and parallelizing the search using <a href="https://docs.rs/rayon/latest/rayon/">rayon</a>
and a <a href="https://doc.rust-lang.org/std/sync/mpsc/fn.channel.html">multiple-producer, single-consumer channel</a> to write results to an output
(either the terminal or a file).
This version took 11 hours to run,
using less than 5GB of RAM and 32 processors,
to search 2k MAGs against 402k metagenomes.
And, bonus! It can also be parallelized again if you have multiple machines,
so it potentially takes a bit more than an hour to run if you can allocate 10 batch jobs,
with each batch 1/10 of the metagenome signatures.</p>
<h2>So, is bottom-up always the better choice?</h2>
<p>I would like to answer "Yes!",
but bioinformatics software tends to be organized as command line interfaces,
not as libraries.
Libraries also tend to have even less documentation than CLIs,
and this particular case is not a fair comparison because...
Well, I wrote most of the library,
and the Rust API is not that well documented for general use.</p>
<p>But I'm pretty happy with how the sourmash CLI is viable both for the top-down approach
(and whatever workflow software you want to use) as well as how the Rust core worked for the bottom-up approach.
I think the most important is having the option to choose which way to go,
especially because now I can use the bottom-up approach to make the sourmash CLI
and Python API better.
The top-down approach is also way more accessible in general,
because you can pick your favorite workflow software and use all the tricks you're comfortable with.</p>
<h2>But, what about the results?!?!?!</h2>
<p>Next time. But I did find MAGs with over 90% containment in very different locations,
which is pretty exciting!</p>
<p>I also need to find a better way of distributing all these signature,
because storing 4 TB of data in S3 is somewhat cheap,
but transferring data is very expensive.
All signatures are also available on IPFS,
but I need more people to host them and share.
Get in contact if you're interested in helping =]</p>
<p>And while I'm asking for help,
any tips on pulling data faster from the SRA are greatly appreciated!</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://twitter.com/luizirber/status/1285782732790849537">Thread on Twitter</a></li>
</ul><hr /><h2>Footnotes</h2><ol><li id="sf-mag-search-1"><p>pulling about a 100 TB in 3 days, which was pretty fun to see because I
ended up DDoS myself because I couldn't download the generated sigs fast enough
from the S3 bucket where they are temporarily stored =P <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-mag-search-1-back" class="simple-footnote-back">↩</a></p></li></ol>
2020-07-22T15:00:00+00:00
luizirber
-
Gabbleblotchits: Putting it all together
https://blog.luizirber.org/2020/05/11/sbt-zip/
<p>sourmash <a href="https://twitter.com/ctitusbrown/status/1257418140729868291">3.3</a> was released last week,
and it is the first version supporting <a href="https://ivory.idyll.org/blog/2020-sourmash-databases-as-zip-files.html">zipped databases</a>.
Here is my personal account of how that came to be =]</p>
<h2>What is a sourmash database?</h2>
<p>A sourmash database contains signatures (typically Scaled MinHash sketches built from genomic datasets) and
an index for allowing efficient similarity and containment queries over these signatures.
The two types of index are SBT,
a hierarchical index that uses less memory by keeping data on disk,
and LCA,
an inverted index that uses more memory but is potentially faster.
Indices are described as JSON files,
with LCA storing all the data in one JSON file and SBT opting for saving a description of the index structure in JSON,
and all the data into a hidden directory with many files.</p>
<p>We distribute some <a href="https://sourmash.readthedocs.io/en/v3.3.0/databases.html">prepared databases</a> (with SBT indices) for Genbank and RefSeq as compressed TAR files.
The compressed file is ~8GB,
but after decompressing it turns into almost 200k files in a hidden directory,
using about 40 GB of disk space.</p>
<h2>Can we avoid generating so many hidden files?</h2>
<p>The initial issue in this saga is <a href="https://github.com/dib-lab/sourmash/issues/490">dib-lab/sourmash#490</a>,
and the idea was to take the existing support for multiple data storages
(hidden dir,
TAR files,
IPFS and Redis) and save the index description in the storage,
allowing loading everything from the storage.
Since we already had the databases as TAR files,
the first test tried to use them but it didn't take long to see it was a doomed approach:
TAR files are terrible from random access
(or at least the <code>tarfile</code> module in Python is).</p>
<p>Zip files showed up as a better alternative,
and it helps that Python has the <code>zipfile</code> module already available in the
standard library.
Initial tests were promising,
and led to <a href="https://github.com/dib-lab/sourmash/pull/648">dib-lab/sourmash#648</a>.
The main issue was performance:
compressing and decompressing was slow,
but there was also another limitation...</p>
<h2>Loading Nodegraphs from a memory buffer</h2>
<p>Another challenge was efficiently loading the data from a storage.
The two core methods in a storage are <code>save(location, content)</code>,
where <code>content</code> is a bytes buffer,
and <code>load(location)</code>,
which returns a bytes buffer that was previously saved.
This didn't interact well with the <code>khmer</code> <code>Nodegraph</code>s (the Bloom Filter we use for SBTs),
since <code>khmer</code> only loads data from files,
not from memory buffers.
We ended up doing a temporary file dance,
which made things slower for the default storage (hidden dir),
where it could have been optimized to work directly with files,
and involved interacting with the filesystem for the other storages
(IPFS and Redis could be pulling data directly from the network,
for example).</p>
<p>This one could be fixed in <code>khmer</code> by exposing C++ stream methods,
and I did a <a href="https://github.com/luizirber/2018-cython-streams">small PoC</a> to test the idea.
While doable,
this is something that was happening while the sourmash conversion to Rust was underway,
and depending on <code>khmer</code> was a problem for my Webassembly aspirations...
so,
having the Nodegraph <a href="https://github.com/luizirber/sourmash-rust/pull/15">implemented in Rust</a> seemed like a better direction,
That has actually been quietly living in the sourmash codebase for quite some time,
but it was never exposed to the Python (and it was also lacking more extensive
tests).</p>
<p>After the release of sourmash 3 and the replacement of the C++ for the Rust implementation,
all the pieces for exposing the Nodegraph where in place,
so <a href="https://github.com/dib-lab/sourmash/pull/799">dib-lab/sourmash#799</a> was the next step.
It wasn't a priority at first because other optimizations
(that were released in 3.1 and 3.2)
were more important,
but then it was time to check how this would perform.
And...</p>
<h2>Your Rust code is not so fast, huh?</h2>
<p>Turns out that my Nodegraph loading code was way slower than <code>khmer</code>.
The Nodegraph binary format <a href="https://khmer.readthedocs.io/en/latest/dev/binary-file-formats.html#nodegrap://khmer.readthedocs.io/en/latest/dev/binary-file-formats.html#nodegraph">is well documented</a>,
and doing an initial implementation wasn't so hard by using the <code>byteorder</code> crate
to read binary data with the right endianess,
and then setting the appropriate bits in the internal <code>fixedbitset</code> in memory.
But the khmer code doesn't parse bit by bit:
it <a href="https://github.com/dib-lab/khmer/blob/fe0ce116456b296c522ba24294a0cabce3b2648b/src/oxli/storage.cc#L233">reads</a> a long <code>char</code> buffer directly,
and that is many orders of magnitude faster than setting bit by bit.</p>
<p>And there was no way to replicate this behavior directly with <code>fixedbitset</code>.
At this point I could either bit-indexing into a large buffer
and lose all the useful methods that <code>fixedbitset</code> provides,
or try to find a way to support loading the data directly into <code>fixedbitset</code> and
open a PR.</p>
<p><a href="https://github.com/petgraph/fixedbitset/pull/42">I chose the PR</a> (and even got #42! =]).</p>
<p>It was more straightforward than I expected,
but it did expose the internal representation of <code>fixedbitset</code>,
so I was a bit nervous it wasn't going to be merged.
But <a href="https://github.com/bluss">bluss</a> was super nice,
and his suggestions made the PR way better!
This <a href="https://github.com/dib-lab/sourmash/blob/9a695fb03b99c060bb8d1384ab78bb3797c5eb65/src/core/src/sketch/nodegraph.rs#L235L261">simplified</a> the final <code>Nodegraph</code> code,
and actually was more correct
(because I was messing a few corner cases when doing the bit-by-bit parsing before).
Win-win!</p>
<h2>Nodegraphs are kind of large, can we compress them?</h2>
<p>Being able to save and load <code>Nodegraph</code>s in Rust allowed using memory buffers,
but also opened the way to support other operations not supported in khmer <code>Nodegraph</code>s.
One example is loading/saving compressed files,
which is supported for <code>Countgraph</code>
(another khmer data structure,
based on Count-Min Sketch)
but not in <code>Nodegraph</code>.</p>
<p>If only there was an easy way to support working with compressed files...</p>
<p>Oh wait, there is! <a href="https://github.com/luizirber/niffler">niffler</a> is a crate that I made with <a href="https://twitter.com/pierre_marijon">Pierre Marijon</a> based
on some functionality I saw in one of his projects,
and we iterated a bit on the API and documented everything to make it more
useful for a larger audience.
<code>niffler</code> tries to be as transparent as possible,
with very little boilerplate when using it but with useful features nonetheless
(like auto detection of the compression format).
If you want more about the motivation and how it happened,
check this <a href="https://twitter.com/luizirber/status/1253445504622424064">Twitter thread</a>.</p>
<p>The cool thing is that adding compressed files support in <code>sourmash</code> was mostly
<a href="https://github.com/dib-lab/sourmash/pull/799/files#diff-313a7ff0fdb14f408a64b3f010f46f65R220">one-line changes</a> for loading
(and <a href="https://github.com/dib-lab/sourmash/pull/648/files#diff-d80ae1dd777d07300d7b6066b3318397L249-R273">a bit more</a> for saving,
but mostly because converting compression levels could use some refactoring).</p>
<h2>Putting it all together: zipped SBT indices</h2>
<p>With all these other pieces in places,
it's time to go back to <a href="https://github.com/dib-lab/sourmash/pull/648">dib-lab/sourmash#648</a>.
Compressing and decompressing with the Python <code>zipfile</code> module is slow,
but Zip files can also be used just for storage,
handing back the data without extracting it.
And since we have compression/decompression implemented in Rust with <code>niffler</code>,
that's what the zipped sourmash databases are:
data is loaded and saved into the Zip file without using the Python module
compression/decompression,
and all the work is done before (or after) in the Rust side.</p>
<p>This allows keeping the Zip file with similar sizes to the original TAR files we started with,
but with very low overhead for decompression.
For compression we opted for using Gzip level 1,
which doesn't compress perfectly but also doesn't take much longer to run:</p>
<table>
<thead>
<tr>
<th>Level</th>
<th>Size</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>407 MB</td>
<td>16s</td>
</tr>
<tr>
<td>1</td>
<td>252 MB</td>
<td>21s</td>
</tr>
<tr>
<td>5</td>
<td>250 MB</td>
<td>39s</td>
</tr>
<tr>
<td>9</td>
<td>246 MB</td>
<td>1m48s</td>
</tr>
</tbody>
</table>
<p>In this table, <code>0</code> is without compression,
while <code>9</code> is the best compression.
The size difference from <code>1</code> to <code>9</code> is only 6 MB (~2% difference)
but runs 5x faster,
and it's only 30% slower than saving the uncompressed data.</p>
<p>The last challenge was updating an existing Zip file.
It's easy to support appending new data,
but if any of the already existing data in the file changes
(which happens when internal nodes change in the SBT,
after a new dataset is inserted) then there is no easy way to replace the data in the Zip file.
Worse,
the Python <code>zipfile</code> will add the new data while keeping the old one around,
leading to ginormous files over time<sup id="sf-sbt-zip-1-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sbt-zip-1" class="simple-footnote" title="The zipfile module does throw a UserWarning pointing that duplicated files were inserted, which is useful during development but generally doesn't show during regular usage...">1</a></sup>
So, what to do?</p>
<p>I ended up opting for dealing with the complexity and <a href="https://github.com/dib-lab/sourmash/pull/648/files#diff-a99b088adcc872e1b408fbdcca20ebebR110-R248">complicating the ZipStorage</a> implementation a bit,
by keeping a buffer for new data.
If it's a new file or it already exists but there are no insertions
the buffer is ignored and all works as before.</p>
<p>If the file exists and new data is inserted,
then it is first stored in the buffer
(where it might also replace a previous entry with the same name).
In this case we also need to check the buffer when trying to load some data
(because it might exist only in the buffer,
and not in the original file).</p>
<p>Finally,
when the <code>ZipStorage</code> is closed it needs to verify if there are new items in the buffer.
If not,
it is safe just to close the original file.
If there are new items but they were not present in the original file,
then we can append the new data to the original file.
The final case is if there are new items that were also in the original file,
and in this case a new Zip file is created and all the content from buffer and
original file are copied to it,
prioritizing items from the buffer.
The original file is replaced by the new Zip file.</p>
<p>Turns out this worked quite well! And so the PR was merged =]</p>
<h2>The future</h2>
<p>Zipped databases open the possibility of distributing extra data that might be
useful for some kinds of analysis.
One thing we are already considering is adding <a href="https://github.com/dib-lab/sourmash/issues/969">taxonomy information</a>,
let's see what else shows up.</p>
<p>Having <code>Nodegraph</code> in Rust is also pretty exciting,
because now we can change the internal representation for something that uses
less memory (maybe using <a href="https://alexbowe.com/rrr/">RRR encoding</a>?),
but more importantly:
now they can also be used with Webassembly,
which opens many possibilities for running not only <a href="https://blog.luizirber.org/2018/08/27/sourmash-wasm/">signature computation</a> but
also <code>search</code> and <code>gather</code> in the browser,
since now we have all the pieces to build it.</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://twitter.com/luizirber/status/1260031886744621059">Thread on Twitter</a></li>
</ul><hr /><h2>Footnotes</h2><ol><li id="sf-sbt-zip-1"><p>The <code>zipfile</code> module does throw a <code>UserWarning</code> pointing that duplicated files were inserted,
which is useful during development but generally doesn't show during regular usage... <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sbt-zip-1-back" class="simple-footnote-back">↩</a></p></li></ol>
2020-05-11T15:00:00+00:00
luizirber
-
PythonClub: Criando um CI de uma aplicação Django usando Github Actions
https://pythonclub.com.br/django-ci-github-actions.html
<p>Fala pessoal, tudo bom?</p>
<p>Nos vídeo abaixo vou mostrar como podemos configurar um CI de uma aplicação Django usando Github Actions.</p>
<p><a class="reference external" href="https://www.youtube.com/watch?v=KpSlY8leYFY">https://www.youtube.com/watch?v=KpSlY8leYFY</a>.</p>
<div class="youtube youtube-16x9"></div>
2020-01-24T15:10:00+00:00
Lucas Magnum
-
Gabbleblotchits: Oxidizing sourmash: PR walkthrough
https://blog.luizirber.org/2020/01/10/sourmash-pr/
<p>sourmash 3 was released last week,
finally landing the Rust backend.
But, what changes when developing new features in sourmash?
I was thinking about how to best document this process,
and since <a href="https://github.com/dib-lab/sourmash/pull/826/files">PR #826</a> is a short example touching all the layers I decided to do a
small walkthrough.</p>
<p>Shall we?</p>
<h2>The problem</h2>
<p>The first step is describing the problem,
and trying to convince reviewers (and yourself) that the changes bring enough benefits to justify a merge.
This is the description I put in the PR:</p>
<blockquote>
<p>Calling <code>.add_hash()</code> on a MinHash sketch is fine,
but if you're calling it all the time it's better to pass a list of hashes and call <code>.add_many()</code> instead.
Before this PR <code>add_many</code> just called <code>add_hash</code> for each hash it was passed,
but now it will pass the full list to Rust (and that's way faster).</p>
<p>No changes for public APIs,
and I changed the <code>_signatures</code> method in LCA to accumulate hashes for each sig first,
and then set them all at once.
This is way faster,
but might use more intermediate memory (I'll evaluate this now).</p>
</blockquote>
<p>There are many details that sound like jargon for someone not familiar with the codebase,
but if I write something too long I'll probably be wasting the reviewers time too.
The benefit of a very detailed description is extending the knowledge for other people
(not necessarily the maintainers),
but that also takes effort that might be better allocated to solve other problems.
Or, more realistically, putting out other fires =P</p>
<p>Nonetheless,
some points I like to add in PR descriptions:
- why is there a problem with the current approach?
- is this the minimal viable change, or is it trying to change too many things
at once? The former is way better, in general.
- what are the trade-offs? This PR is using more memory to lower the runtime,
but I hadn't measure it yet when I opened it.
- Not changing public APIs is always good to convince reviewers.
If the project follows a <a href="https://semver.org/">semantic versioning</a> scheme,
changes to the public APIs are major version bumps,
and that can brings other consequences for users.</p>
<h2>Setting up for changing code</h2>
<p>If this was a bug fix PR,
the first thing I would do is write a new test triggering the bug,
and then proceed to fix it in the code
(Hmm, maybe that would be another good walkthrough?).
But this PR is making performance claims ("it's going to be faster"),
and that's a bit hard to codify in tests.
<sup id="sf-sourmash-pr-1-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-1" class="simple-footnote" title="We do have https://asv.readthedocs.io/ set up for micro-benchmarks, and now that I think about it... I could have started by writing a benchmark for add_many, and then showing that it is faster. I will add this approach to the sourmash PR checklist =]">1</a></sup>
Since it's also proposing to change a method (<code>_signatures</code> in LCA indices) that is better to benchmark with a real index (and not a toy example),
I used the same data and command I run in <a href="https://github.com/luizirber/sourmash_resources">sourmash_resources</a> to check how memory consumption and runtime changed.
For reference, this is the command: </p>
<div class="highlight"><pre><span></span>sourmash search -o out.csv --scaled <span class="m">2000</span> -k <span class="m">51</span> HSMA33OT.fastq.gz.sig genbank-k51.lca.json.gz
</pre></div>
<p>I'm using the <code>benchmark</code> feature from <a href="https://snakemake.readthedocs.io/">snakemake</a> in <a href="https://github.com/luizirber/sourmash_resources/blob/83ea237397d242e48c9d95eb0d9f50ceb4ad95c7/Snakefile#L99L114">sourmash_resources</a> to
track how much memory, runtime and I/O is used for each command (and version) of sourmash,
and generate the plots in the README in that repo.
That is fine for a high-level view ("what's the maximum memory used?"),
but not so useful for digging into details ("what method is consuming most memory?").</p>
<p>Another additional problem is the dual<sup id="sf-sourmash-pr-2-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-2" class="simple-footnote" title="or triple, if you count C">2</a></sup> language nature of sourmash,
where we have Python calling into Rust code (via CFFI).
There are great tools for measuring and profiling Python code,
but they tend to not work with extension code...</p>
<p>So, let's bring two of my favorite tools to help!</p>
<h3>Memory profiling: heaptrack</h3>
<p><a href="https://github.com/KDE/heaptrack">heaptrack</a> is a heap profiler, and I first heard about it from <a href="https://www.vincentprouillet.com/">Vincent Prouillet</a>.
Its main advantage over other solutions (like valgrind's massif) is the low
overhead and... how easy it is to use:
just stick <code>heaptrack</code> in front of your command,
and you're good to go!</p>
<p>Example output:</p>
<div class="highlight"><pre><span></span>$ heaptrack sourmash search -o out.csv --scaled <span class="m">2000</span> -k <span class="m">51</span> HSMA33OT.fastq.gz.sig genbank-k51.lca.json.gz
heaptrack stats:
allocations: <span class="m">1379353</span>
leaked allocations: <span class="m">1660</span>
temporary allocations: <span class="m">168984</span>
Heaptrack finished! Now run the following to investigate the data:
heaptrack --analyze heaptrack.sourmash.66565.gz
</pre></div>
<p><code>heaptrack --analyze</code> is a very nice graphical interface for analyzing the results,
but for this PR I'm mostly focusing on the Summary page (and overall memory consumption).
Tracking allocations in Python doesn't give many details,
because it shows the CPython functions being called,
but the ability to track into the extension code (Rust) allocations is amazing
for finding bottlenecks (and memory leaks =P).
<sup id="sf-sourmash-pr-3-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-3" class="simple-footnote" title="It would be super cool to have the unwinding code from py-spy in heaptrack, and be able to see exactly what Python methods/lines of code were calling the Rust parts...">3</a></sup></p>
<h3>CPU profiling: py-spy</h3>
<p>Just as other solutions exist for profiling memory,
there are many for profiling CPU usage in Python,
including <code>profile</code> and <code>cProfile</code> in the standard library.
Again, the issue is being able to analyze extension code,
and bringing the cannon (the <code>perf</code> command in Linux, for example) looses the
benefit of tracking Python code properly (because we get back the CPython
functions, not what you defined in your Python code).</p>
<p>Enters <a href="https://github.com/benfred/py-spy">py-spy</a> by <a href="https://www.benfrederickson.com">Ben Frederickson</a>,
based on the <a href="https://github.com/rbspy/rbspy">rbspy</a> project by <a href="https://jvns.ca">Julia Evans</a>.
Both use a great idea:
read the process maps for the interpreters and resolve the full stack trace information,
with low overhead (because it uses sampling).
<a href="https://github.com/benfred/py-spy">py-spy</a> also goes further and resolves <a href="https://www.benfrederickson.com/profiling-native-python-extensions-with-py-spy/">native Python extensions</a> stack traces,
meaning we can get the complete picture all the way from the Python CLI to the
Rust core library!<sup id="sf-sourmash-pr-4-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-4" class="simple-footnote" title="Even if py-spy doesn't talk explicitly about Rust, it works very very well, woohoo!">4</a></sup></p>
<p><code>py-spy</code> is also easy to use:
stick <code>py-spy record --output search.svg -n --</code> in front of the command,
and it will generate a flamegraph in <code>search.svg</code>.
The full command for this PR is</p>
<div class="highlight"><pre><span></span>py-spy record --output search.svg -n -- sourmash search -o out.csv --scaled <span class="m">2000</span> -k <span class="m">51</span> HSMA.fastq.sig genbank-k51.lca.json.gz
</pre></div>
<h2>Show me the code!</h2>
<p>OK, OK, sheesh. But it's worth repeating: the code is important, but there are
many other aspects that are just as important =]</p>
<h3>Replacing <code>add_hash</code> calls with one <code>add_many</code></h3>
<p>Let's start at the <a href="https://github.com/dib-lab/sourmash/pull/826/files#diff-adf06d14c535d5b22da9fb3862e4a487"><code>_signatures()</code></a> method on LCA indices.
This is the original method:</p>
<div class="highlight"><pre><span></span><span class="nd">@cached_property</span>
<span class="k">def</span> <span class="nf">_signatures</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="s2">"Create a _signatures member dictionary that contains {idx: minhash}."</span>
<span class="kn">from</span> <span class="nn">..</span> <span class="kn">import</span> <span class="n">MinHash</span>
<span class="n">minhash</span> <span class="o">=</span> <span class="n">MinHash</span><span class="p">(</span><span class="n">n</span><span class="o">=</span><span class="mi">0</span><span class="p">,</span> <span class="n">ksize</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">ksize</span><span class="p">,</span> <span class="n">scaled</span><span class="o">=</span><span class="bp">self</span><span class="o">.</span><span class="n">scaled</span><span class="p">)</span>
<span class="n">debug</span><span class="p">(</span><span class="s1">'creating signatures for LCA DB...'</span><span class="p">)</span>
<span class="n">sigd</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="n">minhash</span><span class="o">.</span><span class="n">copy_and_clear</span><span class="p">)</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="bp">self</span><span class="o">.</span><span class="n">hashval_to_idx</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="k">for</span> <span class="n">vv</span> <span class="ow">in</span> <span class="n">v</span><span class="p">:</span>
<span class="n">sigd</span><span class="p">[</span><span class="n">vv</span><span class="p">]</span><span class="o">.</span><span class="n">add_hash</span><span class="p">(</span><span class="n">k</span><span class="p">)</span>
<span class="n">debug</span><span class="p">(</span><span class="s1">'=> </span><span class="si">{}</span><span class="s1"> signatures!'</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">sigd</span><span class="p">))</span>
<span class="k">return</span> <span class="n">sigd</span>
</pre></div>
<p><code>sigd[vv].add_hash(k)</code> is the culprit.
Each call to <code>.add_hash</code> has to go thru CFFI to reach the extension code,
and the overhead is significant.
It is a similar situation to accessing array elements in NumPy:
it works,
but it is way slower than using operations that avoid crossing from Python to
the extension code.
What we want to do instead is call <code>.add_many(hashes)</code>,
which takes a list of hashes and process it entirely in Rust
(ideally. We will get there).</p>
<p>But, to have a list of hashes, there is another issue with this code.</p>
<div class="highlight"><pre><span></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="bp">self</span><span class="o">.</span><span class="n">hashval_to_idx</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="k">for</span> <span class="n">vv</span> <span class="ow">in</span> <span class="n">v</span><span class="p">:</span>
<span class="n">sigd</span><span class="p">[</span><span class="n">vv</span><span class="p">]</span><span class="o">.</span><span class="n">add_hash</span><span class="p">(</span><span class="n">k</span><span class="p">)</span>
</pre></div>
<p>There are two nested for loops,
and <code>add_hash</code> is being called with values from the inner loop.
So... we don't have the list of hashes beforehand.</p>
<p>But we can change the code a bit to save the hashes for each signature
in a temporary list,
and then call <code>add_many</code> on the temporary list.
Like this:</p>
<div class="highlight"><pre><span></span><span class="n">temp_vals</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">list</span><span class="p">)</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="bp">self</span><span class="o">.</span><span class="n">hashval_to_idx</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="k">for</span> <span class="n">vv</span> <span class="ow">in</span> <span class="n">v</span><span class="p">:</span>
<span class="n">temp_vals</span><span class="p">[</span><span class="n">vv</span><span class="p">]</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">k</span><span class="p">)</span>
<span class="k">for</span> <span class="n">sig</span><span class="p">,</span> <span class="n">vals</span> <span class="ow">in</span> <span class="n">temp_vals</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="n">sigd</span><span class="p">[</span><span class="n">sig</span><span class="p">]</span><span class="o">.</span><span class="n">add_many</span><span class="p">(</span><span class="n">vals</span><span class="p">)</span>
</pre></div>
<p>There is a trade-off here:
if we save the hashes in temporary lists,
will the memory consumption be so high that the runtime gains of calling
<code>add_many</code> in these temporary lists be cancelled?</p>
<p>Time to measure it =]</p>
<table>
<thead>
<tr>
<th align="left">version</th>
<th align="left">mem</th>
<th align="left">time</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">original</td>
<td align="left">1.5 GB</td>
<td align="left">160s</td>
</tr>
<tr>
<td align="left"><code>list</code></td>
<td align="left">1.7GB</td>
<td align="left">173s</td>
</tr>
</tbody>
</table>
<p>Wait, it got worse?!?! Building temporary lists only takes time and memory,
and bring no benefits!</p>
<p>This mystery goes away when you look at the <a href="https://github.com/dib-lab/sourmash/pull/826/files#diff-2f53b2a5be4083c39a0275847c87f88fR190">add_many method</a>:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">add_many</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">hashes</span><span class="p">):</span>
<span class="s2">"Add many hashes in at once."</span>
<span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">hashes</span><span class="p">,</span> <span class="n">MinHash</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_methodcall</span><span class="p">(</span><span class="n">lib</span><span class="o">.</span><span class="n">kmerminhash_add_from</span><span class="p">,</span> <span class="n">hashes</span><span class="o">.</span><span class="n">_objptr</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="k">for</span> <span class="nb">hash</span> <span class="ow">in</span> <span class="n">hashes</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_methodcall</span><span class="p">(</span><span class="n">lib</span><span class="o">.</span><span class="n">kmerminhash_add_hash</span><span class="p">,</span> <span class="nb">hash</span><span class="p">)</span>
</pre></div>
<p>The first check in the <code>if</code> statement is a shortcut for adding hashes from
another <code>MinHash</code>, so let's focus on <code>else</code> part...
And turns out that <code>add_many</code> is lying!
It doesn't process the <code>hashes</code> in the Rust extension,
but just loops and call <code>add_hash</code> for each <code>hash</code> in the list.
That's not going to be any faster than what we were doing in <code>_signatures</code>.</p>
<p>Time to fix <code>add_many</code>!</p>
<h3>Oxidizing <code>add_many</code></h3>
<p>The idea is to change this loop in <code>add_many</code>:</p>
<div class="highlight"><pre><span></span><span class="k">for</span> <span class="nb">hash</span> <span class="ow">in</span> <span class="n">hashes</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_methodcall</span><span class="p">(</span><span class="n">lib</span><span class="o">.</span><span class="n">kmerminhash_add_hash</span><span class="p">,</span> <span class="nb">hash</span><span class="p">)</span>
</pre></div>
<p>with a call to a Rust extension function:</p>
<div class="highlight"><pre><span></span><span class="bp">self</span><span class="o">.</span><span class="n">_methodcall</span><span class="p">(</span><span class="n">lib</span><span class="o">.</span><span class="n">kmerminhash_add_many</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">hashes</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">hashes</span><span class="p">))</span>
</pre></div>
<p><code>self._methodcall</code> is a convenience method defined in <a href="https://github.com/dib-lab/sourmash/blob/c6cbdf0398ef836797492e13371a38373c544ae1/sourmash/utils.py#L24">RustObject</a>
which translates a method-like call into a function call,
since our C layer only has functions.
This is the C prototype for this function:</p>
<div class="highlight"><pre><span></span><span class="kt">void</span><span class="w"> </span><span class="nf">kmerminhash_add_many</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">KmerMinHash</span><span class="w"> </span><span class="o">*</span><span class="n">ptr</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint64_t</span><span class="w"> </span><span class="o">*</span><span class="n">hashes_ptr</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="kt">uintptr_t</span><span class="w"> </span><span class="n">insize</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
</pre></div>
<p>You can almost read it as a Python method declaration,
where <code>KmerMinHash *ptr</code> means the same as the <code>self</code> in Python methods.
The other two arguments are a common idiom when passing pointers to data in C,
with <code>insize</code> being how many elements we have in the list.
<sup id="sf-sourmash-pr-5-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-5" class="simple-footnote" title="Let's not talk about lack of array bounds checks in C...">5</a></sup>.
<code>CFFI</code> is very good at converting Python lists into pointers of a specific type,
as long as the type is of a primitive type
(<code>uint64_t</code> in our case, since each hash is a 64-bit unsigned integer number).</p>
<p>And the Rust code with the implementation of the function:</p>
<div class="highlight"><pre><span></span><span class="n">ffi_fn</span><span class="o">!</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="k">unsafe</span><span class="w"> </span><span class="k">fn</span> <span class="nf">kmerminhash_add_many</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">ptr</span>: <span class="o">*</span><span class="k">mut</span><span class="w"> </span><span class="n">KmerMinHash</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">hashes_ptr</span>: <span class="o">*</span><span class="k">const</span><span class="w"> </span><span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">insize</span>: <span class="kt">usize</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"> </span>-> <span class="nb">Result</span><span class="o"><</span><span class="p">()</span><span class="o">></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">mh</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="fm">assert!</span><span class="p">(</span><span class="o">!</span><span class="n">ptr</span><span class="p">.</span><span class="n">is_null</span><span class="p">());</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="o">*</span><span class="n">ptr</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">hashes</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="fm">assert!</span><span class="p">(</span><span class="o">!</span><span class="n">hashes_ptr</span><span class="p">.</span><span class="n">is_null</span><span class="p">());</span><span class="w"></span>
<span class="w"> </span><span class="n">slice</span>::<span class="n">from_raw_parts</span><span class="p">(</span><span class="n">hashes_ptr</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="o">*</span><span class="k">mut</span><span class="w"> </span><span class="kt">u64</span><span class="p">,</span><span class="w"> </span><span class="n">insize</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">for</span><span class="w"> </span><span class="n">hash</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">hashes</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">mh</span><span class="p">.</span><span class="n">add_hash</span><span class="p">(</span><span class="o">*</span><span class="n">hash</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="nb">Ok</span><span class="p">(())</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</pre></div>
<p>Let's break what's happening here into smaller pieces.
Starting with the function signature:</p>
<div class="highlight"><pre><span></span><span class="n">ffi_fn</span><span class="o">!</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="k">unsafe</span><span class="w"> </span><span class="k">fn</span> <span class="nf">kmerminhash_add_many</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">ptr</span>: <span class="o">*</span><span class="k">mut</span><span class="w"> </span><span class="n">KmerMinHash</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">hashes_ptr</span>: <span class="o">*</span><span class="k">const</span><span class="w"> </span><span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">insize</span>: <span class="kt">usize</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="p">)</span><span class="w"> </span>-> <span class="nb">Result</span><span class="o"><</span><span class="p">()</span><span class="o">></span><span class="w"></span>
</pre></div>
<p>The weird <code>ffi_fn! {}</code> syntax around the function is a macro in Rust:
it changes the final generated code to convert the return value (<code>Result<()></code>) into something that is valid C code (in this case, <code>void</code>).
What happens if there is an error, then?
The Rust extension has code for passing back an error code and message to Python,
as well as capturing panics (when things go horrible bad and the program can't recover)
in a way that Python can then deal with (raising exceptions and cleaning up).
It also sets the <code>#[no_mangle]</code> attribute in the function,
meaning that the final name of the function will follow C semantics (instead of Rust semantics),
and can be called more easily from C and other languages.
This <code>ffi_fn!</code> macro comes from <a href="https://github.com/getsentry/symbolic">symbolic</a>,
a big influence on the design of the Python/Rust bridge in sourmash.</p>
<p><code>unsafe</code> is the keyword in Rust to disable some checks in the code to allow
potentially dangerous things (like dereferencing a pointer),
and it is required to interact with C code.
<code>unsafe</code> doesn't mean that the code is always unsafe to use:
it's up to whoever is calling this to verify that valid data is being passed and invariants are being preserved.</p>
<p>If we remove the <code>ffi_fn!</code> macro and the <code>unsafe</code> keyword,
we have</p>
<div class="highlight"><pre><span></span><span class="k">fn</span> <span class="nf">kmerminhash_add_many</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">ptr</span>: <span class="o">*</span><span class="k">mut</span><span class="w"> </span><span class="n">KmerMinHash</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">hashes_ptr</span>: <span class="o">*</span><span class="k">const</span><span class="w"> </span><span class="kt">u64</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="n">insize</span>: <span class="kt">usize</span>
<span class="p">);</span><span class="w"></span>
</pre></div>
<p>At this point we can pretty much map between Rust and the C function prototype:</p>
<div class="highlight"><pre><span></span><span class="kt">void</span><span class="w"> </span><span class="nf">kmerminhash_add_many</span><span class="p">(</span><span class="w"></span>
<span class="w"> </span><span class="n">KmerMinHash</span><span class="w"> </span><span class="o">*</span><span class="n">ptr</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint64_t</span><span class="w"> </span><span class="o">*</span><span class="n">hashes_ptr</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="kt">uintptr_t</span><span class="w"> </span><span class="n">insize</span><span class="w"></span>
<span class="w"> </span><span class="p">);</span><span class="w"></span>
</pre></div>
<p>Some interesting points:</p>
<ul>
<li>We use <code>fn</code> to declare a function in Rust.</li>
<li>The type of an argument comes after the name of the argument in Rust,
while it's the other way around in C.
Same for the return type (it is omitted in the Rust function, which means it
is <code>-> ()</code>, equivalent to a <code>void</code> return type in C).</li>
<li>In Rust everything is <strong>immutable</strong> by default, so we need to say that we want
a mutable pointer to a <code>KmerMinHash</code> item: <code>*mut KmerMinHash</code>).
In C everything is mutable by default.</li>
<li><code>u64</code> in Rust -> <code>uint64_t</code> in C</li>
<li><code>usize</code> in Rust -> <code>uintptr_t</code> in C</li>
</ul>
<p>Let's check the implementation of the function now.
We start by converting the <code>ptr</code> argument (a raw pointer to a <code>KmerMinHash</code> struct)
into a regular Rust struct:</p>
<div class="highlight"><pre><span></span><span class="kd">let</span><span class="w"> </span><span class="n">mh</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="fm">assert!</span><span class="p">(</span><span class="o">!</span><span class="n">ptr</span><span class="p">.</span><span class="n">is_null</span><span class="p">());</span><span class="w"></span>
<span class="w"> </span><span class="o">&</span><span class="k">mut</span><span class="w"> </span><span class="o">*</span><span class="n">ptr</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
</pre></div>
<p>This block is asserting that <code>ptr</code> is not a null pointer,
and if so it dereferences it and store in a mutable reference.
If it was a null pointer the <code>assert!</code> would panic (which might sound extreme,
but is way better than continue running because dereferencing a null pointer is
BAD).
Note that functions always need all the types in arguments and return values,
but for variables in the body of the function
Rust can figure out types most of the time,
so no need to specify them.</p>
<p>The next block prepares our list of hashes for use:</p>
<div class="highlight"><pre><span></span><span class="kd">let</span><span class="w"> </span><span class="n">hashes</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="fm">assert!</span><span class="p">(</span><span class="o">!</span><span class="n">hashes_ptr</span><span class="p">.</span><span class="n">is_null</span><span class="p">());</span><span class="w"></span>
<span class="w"> </span><span class="n">slice</span>::<span class="n">from_raw_parts</span><span class="p">(</span><span class="n">hashes_ptr</span><span class="w"> </span><span class="k">as</span><span class="w"> </span><span class="o">*</span><span class="k">mut</span><span class="w"> </span><span class="kt">u64</span><span class="p">,</span><span class="w"> </span><span class="n">insize</span><span class="p">)</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
</pre></div>
<p>We are again asserting that the <code>hashes_ptr</code> is not a null pointer,
but instead of dereferencing the pointer like before we use it to create a <code>slice</code>,
a dynamically-sized view into a contiguous sequence.
The list we got from Python is a contiguous sequence of size <code>insize</code>,
and the <code>slice::from_raw_parts</code> function creates a slice from a pointer to data and a size.</p>
<p>Oh, and can you spot the bug?
I created the slice using <code>*mut u64</code>,
but the data is declared as <code>*const u64</code>.
Because we are in an <code>unsafe</code> block Rust let me change the mutability,
but I shouldn't be doing that,
since we don't need to mutate the slice.
Oops.</p>
<p>Finally, let's add hashes to our MinHash!
We need a <code>for</code> loop, and call <code>add_hash</code> for each <code>hash</code>:</p>
<div class="highlight"><pre><span></span><span class="k">for</span><span class="w"> </span><span class="n">hash</span><span class="w"> </span><span class="k">in</span><span class="w"> </span><span class="n">hashes</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">mh</span><span class="p">.</span><span class="n">add_hash</span><span class="p">(</span><span class="o">*</span><span class="n">hash</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="nb">Ok</span><span class="p">(())</span><span class="w"></span>
</pre></div>
<p>We finish the function with <code>Ok(())</code> to indicate no errors occurred.</p>
<p>Why is calling <code>add_hash</code> here faster than what we were doing before in Python?
Rust can optimize these calls and generate very efficient native code,
while Python is an interpreted language and most of the time don't have the same
guarantees that Rust can leverage to generate the code.
And, again,
calling <code>add_hash</code> here doesn't need to cross FFI boundaries or,
in fact,
do any dynamic evaluation during runtime,
because it is all statically analyzed during compilation.</p>
<h2>Putting it all together</h2>
<p>And... that's the PR code.
There are some other unrelated changes that should have been in new PRs,
but since they were so small it would be more work than necessary.
OK, that's a lame excuse:
it's confusing for reviewers to see these changes here,
so avoid doing that if possible!</p>
<p>But, did it work?</p>
<table>
<thead>
<tr>
<th align="left">version</th>
<th align="left">mem</th>
<th align="left">time</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">original</td>
<td align="left">1.5 GB</td>
<td align="left">160s</td>
</tr>
<tr>
<td align="left"><code>list</code></td>
<td align="left">1.7GB</td>
<td align="left">73s</td>
</tr>
</tbody>
</table>
<p>We are using 200 MB of extra memory,
but taking less than half the time it was taking before.
I think this is a good trade-off,
and so did the <a href="https://github.com/dib-lab/sourmash/pull/826#pullrequestreview-339020803">reviewer</a> and the PR was approved.</p>
<p>Hopefully this was useful, 'til next time!</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://social.lasanha.org/@luizirber/103461534713587975">Thread on Mastodon</a></li>
<li><a href="https://twitter.com/luizirber/status/1215772245928235008">Thread on Twitter</a></li>
<li><a href="https://lobste.rs/s/xnaugq/oxidizing_sourmash_pr_walkthrough">Lobste.rs submission</a></li>
</ul>
<h2>Bonus: <code>list</code> or <code>set</code>?</h2>
<p>The first version of the PR used a <code>set</code> instead of a <code>list</code> to accumulate hashes.
Since a <code>set</code> doesn't have repeated elements,
this could potentially use less memory.
The code:</p>
<div class="highlight"><pre><span></span><span class="n">temp_vals</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">set</span><span class="p">)</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="bp">self</span><span class="o">.</span><span class="n">hashval_to_idx</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="k">for</span> <span class="n">vv</span> <span class="ow">in</span> <span class="n">v</span><span class="p">:</span>
<span class="n">temp_vals</span><span class="p">[</span><span class="n">vv</span><span class="p">]</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">k</span><span class="p">)</span>
<span class="k">for</span> <span class="n">sig</span><span class="p">,</span> <span class="n">vals</span> <span class="ow">in</span> <span class="n">temp_vals</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="n">sigd</span><span class="p">[</span><span class="n">sig</span><span class="p">]</span><span class="o">.</span><span class="n">add_many</span><span class="p">(</span><span class="n">vals</span><span class="p">)</span>
</pre></div>
<p>The runtime was again half of the original,
but...</p>
<table>
<thead>
<tr>
<th align="left">version</th>
<th align="left">mem</th>
<th align="left">time</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">original</td>
<td align="left">1.5 GB</td>
<td align="left">160s</td>
</tr>
<tr>
<td align="left"><code>set</code></td>
<td align="left">3.8GB</td>
<td align="left">80s</td>
</tr>
<tr>
<td align="left"><code>list</code></td>
<td align="left">1.7GB</td>
<td align="left">73s</td>
</tr>
</tbody>
</table>
<p>... memory consumption was almost 2.5 times the original! WAT</p>
<p>The culprit this time? The new <code>kmerminhash_add_many</code> call in the <code>add_many</code>
method.
This one:</p>
<div class="highlight"><pre><span></span><span class="bp">self</span><span class="o">.</span><span class="n">_methodcall</span><span class="p">(</span><span class="n">lib</span><span class="o">.</span><span class="n">kmerminhash_add_many</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">hashes</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">hashes</span><span class="p">))</span>
</pre></div>
<p><code>CFFI</code> doesn't know how to convert a <code>set</code> into something that C understands,
so we need to call <code>list(hashes)</code> to convert it into a list.
Since Python (and <code>CFFI</code>) can't know if the data is going to be used later
<sup id="sf-sourmash-pr-6-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-6" class="simple-footnote" title="something that the memory ownership model in Rust does, BTW">6</a></sup>
it needs to keep it around
(and be eventually deallocated by the garbage collector).
And that's how we get at least double the memory being allocated...</p>
<p>There is another lesson here.
If we look at the <code>for</code> loop again:</p>
<div class="highlight"><pre><span></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="bp">self</span><span class="o">.</span><span class="n">hashval_to_idx</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
<span class="k">for</span> <span class="n">vv</span> <span class="ow">in</span> <span class="n">v</span><span class="p">:</span>
<span class="n">temp_vals</span><span class="p">[</span><span class="n">vv</span><span class="p">]</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">k</span><span class="p">)</span>
</pre></div>
<p>each <code>k</code> is already unique because they are keys in the <code>hashval_to_idx</code> dictionary,
so the initial assumption
(that a <code>set</code> might save memory because it doesn't have repeated elements)
is... irrelevant for the problem =]</p><hr /><h2>Footnotes</h2><ol><li id="sf-sourmash-pr-1"><p>We do have https://asv.readthedocs.io/ set up for micro-benchmarks,
and now that I think about it...
I could have started by writing a benchmark for <code>add_many</code>,
and then showing that it is faster.
I will add this approach to the sourmash PR checklist =] <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-1-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-pr-2"><p>or triple, if you count C <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-2-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-pr-3"><p>It would be super cool to have the unwinding code from py-spy in heaptrack,
and be able to see exactly what Python methods/lines of code were calling the
Rust parts... <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-3-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-pr-4"><p>Even if py-spy doesn't talk explicitly about Rust,
it works very very well, woohoo! <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-4-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-pr-5"><p>Let's not talk about lack of array bounds checks in C... <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-5-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-pr-6"><p>something that the memory ownership model in Rust does, BTW <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-pr-6-back" class="simple-footnote-back">↩</a></p></li></ol>
2020-01-10T15:00:00+00:00
luizirber
-
Gabbleblotchits: Interoperability #rust2020
https://blog.luizirber.org/2019/12/01/rust-2020/
<p>In January I wrote a <a href="https://blog.luizirber.org/2019/01/05/rust-2019/">post</a> for the Rust 2019 call for blogs.
The <a href="https://blog.rust-lang.org/2019/10/29/A-call-for-blogs-2020.html">2020 call</a> is aiming for an RFC and roadmap earlier this time,
so here is my 2020 post =]</p>
<h3>Last call review: what happened?</h3>
<h4>An attribute proc-macro like <code>#[wasm_bindgen]</code> but for FFI</h4>
<p>This sort of happened... because WebAssembly is growing =]</p>
<p>I was very excited when <a href="https://hacks.mozilla.org/2019/08/webassembly-interface-types/">Interface Types</a> showed up in August,
and while it is still very experimental it is moving fast and bringing saner
paths for interoperability than raw C FFIs.
David Beazley even point this at the end of his <a href="https://www.youtube.com/watch?v=r-A78RgMhZU">PyCon India keynote</a>,
talking about how easy is to get information out of a WebAssembly module
compared to what had to be done for SWIG.</p>
<p>This doesn't solve the problem where strict C compatibility is required,
or for platforms where a WebAssembly runtime is not available,
but I think it is a great solution for scientific software
(or, at least, for my use cases =]).</p>
<h4>"More -sys and Rust-like crates for interoperability with the larger ecosystems" and "More (bioinformatics) tools using Rust!"</h4>
<p>I did some of those this year (<a href="https://crates.io/crates/bbhash-sys">bbhash-sys</a> and <a href="https://crates.io/crates/mqf">mqf</a>),
and also found some great crates to use in my projects.
Rust is picking up steam in bioinformatics,
being used as the primary choice for high quality software
(like <a href="https://varlociraptor.github.io/">varlociraptor</a>,
or the many coming from <a href="https://github.com/10XGenomics/">10X Genomics</a>)
but it is still somewhat hard to find more details
(I mostly find it on Twitter,
and sometime Google Scholar alerts).
It would be great to start bringing this info together,
which leads to...</p>
<h4>"A place to find other scientists?"</h4>
<p>Hey, this one happened! <a href="https://twitter.com/algo_luca/status/1081966759048028162">Luca Palmieri</a> started a conversation on <a href="https://www.reddit.com/r/rust/comments/ae77gt/scientific_computingmachine_learning_do_we_want_a/">reddit</a> and
the <a href="https://discord.gg/EXTSq4v">#science-and-ai</a> Discord channel on the Rust community server was born!
I think it works pretty well,
and Luca also has being doing a great job running <a href="https://github.com/LukeMathWalker/ndarray-koans">workshops</a>
and guiding the conversation around <a href="https://github.com/rust-ml/discussion">rust-ml</a>.</p>
<h2>Rust 2021: Interoperability</h2>
<p>Rust is amazing because it is very good at bringing many concepts and ideas that
seem contradictory at first,
but can really shine when <a href="https://rust-lang.github.io/rustconf-2018-keynote/#127">synthesized</a>.
But can we share this combined wisdom and also improve the situation in other
places too?
Despite the "Rewrite it in Rust" meme,
increased interoperability is something that is already driving a lot of the
best aspects of Rust:</p>
<ul>
<li>
<p>Interoperability with other languages: as I said before,
with WebAssembly (and Rust being having the best toolchain for it)
there is a clear route to achieve this,
but it will not replace all the software that already exist and can benefit
from FFI and C compatibility.
Bringing together developers from the many language specific binding
generators (<a href="https://github.com/tildeio/helix">helix</a>, <a href="https://github.com/neon-bindings/neon">neon</a>, <a href="https://github.com/rusterlium/rustler">rustler</a>, <a href="https://github.com/PyO3/pyo3">PyO3</a>...) and figuring out what's missing from
them (or what is the common parts that can be shared) also seems productive.</p>
</li>
<li>
<p>Interoperability with new and unexplored domains.
I think Rust benefits enormously from not focusing only in one domain,
and choosing to prioritize CLI, WebAssembly, Networking and Embedded is a good
subset to start tackling problems,
but how to guide other domains to also use Rust and come up with new
contributors and expose missing pieces of the larger picture?</p>
</li>
</ul>
<p>Another point extremely close to interoperability is training.
A great way to interoperate with other languages and domains is having good
documentation and material from transitioning into Rust without having to figure
everything at once.
Rust documentation is already amazing,
especially considering the many books published by each working group.
But... there is a gap on the transitions,
both from understanding the basics of the language and using it,
to the progression from beginner to intermediate and expert.</p>
<p>I see good resources for <a href="https://github.com/yoshuawuyts/rust-for-js-people">JavaScript</a> and <a href="https://github.com/rochacbruno/py2rs">Python</a> developers,
but we are still covering a pretty small niche:
programmers curious enough to go learn another language,
or looking for solutions for problems in their current language.</p>
<p>Can we bring more people into Rust?
<a href="https://rustbridge.com/">RustBridge</a> is obviously the reference here,
but there is space for much,
much more.
Using Rust in <a href="https://carpentries.org/">The Carpentries</a> lessons?
Creating <code>RustOpenSci</code>,
mirroring the communities of practice of <a href="https://ropensci.org/about/">rOpenSci</a> and <a href="https://www.pyopensci.org/">pyOpenSci</a>?</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://social.lasanha.org/@luizirber/103236549475802733">Thread on Mastodon</a></li>
<li><a href="https://twitter.com/luizirber/status/1201373423592562690">Thread on Twitter</a></li>
</ul>
2019-12-01T15:00:00+00:00
luizirber
-
PythonClub: Criando dicts a partir de outros dicts
https://pythonclub.com.br/crie_dict-a-partir-de-outros-dicts.html
<p>Neste tutorial, será abordado o processo de criação de um <em>dict</em> ou dicionário, a partir de um ou mais <em>dicts</em> em Python. </p>
<p>Como já é de costume da linguagem, isso pode ser feito de várias maneiras diferentes.</p>
<h2>Abordagem inicial</h2>
<p>Pra começar, vamos supor que temos os seguintes dicionários:</p>
<div class="highlight"><pre><span></span><span class="n">dict_1</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'a'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s1">'b'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="p">}</span>
<span class="n">dict_2</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'b'</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="s1">'c'</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>Como exemplo, vamos criar um novo dicionário chamado <strong>new_dict</strong> com os valores de <strong>dict_1</strong> e <strong>dict_2</strong> logo acima. Uma abordagem bem conhecida é utilizar o método <em>update</em>.</p>
<div class="highlight"><pre><span></span><span class="n">new_dict</span> <span class="o">=</span> <span class="p">{}</span>
<span class="n">new_dcit</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">dict_1</span><span class="p">)</span>
<span class="n">new_dcit</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">dict_2</span><span class="p">)</span>
</pre></div>
<p>Assim, temos que <strong>new_dict</strong> será:</p>
<div class="highlight"><pre><span></span><span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">new_dict</span><span class="p">)</span>
<span class="p">{</span>
<span class="s1">'a'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s1">'b'</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="s1">'c'</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>Este método funciona bem, porém temos de chamar o método <em>update</em> para cada <em>dict</em> que desejamos mesclar em <strong>new_dict</strong>. Não seria interessante se fosse possível passar todos os <em>dicts</em> necessários já na inicialização de <strong>new_dict</strong>?</p>
<h3>Novidades do Python 3</h3>
<p>O Python 3 introduziu uma maneira bem interessante de se fazer isso, utilizando os operadores <code>**</code>.</p>
<div class="highlight"><pre><span></span><span class="n">new_dict</span> <span class="o">=</span> <span class="p">{</span>
<span class="o">**</span><span class="n">dict_1</span><span class="p">,</span>
<span class="o">**</span><span class="n">dict_2</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>Assim, de maneira semelhante ao exemplo anterior, temos que <strong>new_dict</strong> será :</p>
<div class="highlight"><pre><span></span><span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">new_dict</span><span class="p">[</span><span class="s1">'a'</span><span class="p">])</span>
<span class="mi">1</span>
<span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">new_dict</span><span class="p">[</span><span class="s1">'b'</span><span class="p">])</span>
<span class="mi">3</span>
<span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">new_dict</span><span class="p">[</span><span class="s1">'c'</span><span class="p">])</span>
<span class="mi">4</span>
</pre></div>
<h2>Cópia real de <em>dicts</em></h2>
<p>Ao utilizamos o procedimento de inicialização acima, devemos tomar conseiderar alguns fatores. Apenas os valores do primeiro nível serão realmente duplicados no novo dicionário. Como exemplo, vamos alterar uma chave presente em ambos os <em>dicts</em> e verificar se as mesmas possuem o mesmo valor:</p>
<div class="highlight"><pre><span></span><span class="o">>></span> <span class="n">dict_1</span><span class="p">[</span><span class="s1">'a'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">10</span>
<span class="o">>></span> <span class="n">new_dict</span><span class="p">[</span><span class="s1">'a'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">11</span>
<span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">dict_1</span><span class="p">[</span><span class="s1">'a'</span><span class="p">])</span>
<span class="mi">10</span>
<span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">new_dict</span><span class="p">[</span><span class="s1">'a'</span><span class="p">])</span>
<span class="mi">11</span>
</pre></div>
<p>Porém isso muda quando um dos valores de <strong>dict_1</strong> for uma <em>list</em>, outro <em>dict</em> ou algum objeto complexo. Por exemplo:</p>
<div class="highlight"><pre><span></span><span class="n">dict_3</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'a'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s1">'b'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="s1">'c'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'d'</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
</pre></div>
<p>e agora, vamos criar um novo <em>dict</em> a partir desse:</p>
<div class="highlight"><pre><span></span><span class="n">new_dict</span> <span class="o">=</span> <span class="p">{</span>
<span class="o">**</span><span class="n">dict_3</span><span class="p">,</span>
<span class="p">}</span>
</pre></div>
<p>Como no exemplo anterior, podemos imaginar que foi realizado uma cópia de todos os elementos de <strong>dict_3</strong>, porém isso não é totalmente verdade. O que realmente aconteceu é que foi feita uma cópia <em>superficial</em> dos valores de <strong>dict_3</strong>, ou seja, apenas os valores de <em>primeiro nível</em> foram duplicados. Observe o que acontece quando alteramos o valor do <em>dict</em> presente na chave <strong>c</strong>.</p>
<div class="highlight"><pre><span></span><span class="o">>></span> <span class="n">new_dict</span><span class="p">[</span><span class="s1">'c'</span><span class="p">][</span><span class="s1">'d'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">11</span>
<span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">new_dict</span><span class="p">[</span><span class="s1">'c'</span><span class="p">][</span><span class="s1">'d'</span><span class="p">])</span>
<span class="mi">11</span>
<span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">dict_3</span><span class="p">[</span><span class="s1">'c'</span><span class="p">][</span><span class="s1">'d'</span><span class="p">])</span>
<span class="mi">11</span>
<span class="c1"># valor anterior era 5</span>
</pre></div>
<p>No caso da chave <strong>c</strong>, ela contem uma referência para outra estrutura de dados (um <em>dict</em>, no caso). Quando alteramos algum valor de <strong>dict_3['c']</strong>, isso reflete em todos os <em>dict</em> que foram inicializados com <strong>dict_3</strong>. Em outras palavras, deve-se ter cuidado ao inicializar um <em>dict</em> a partir de outros <strong>dicts</strong> quando os mesmos possuírem valores complexos, como <em>list</em>, <em>dict</em> ou outros objetos (os atributos deste objeto não serão duplicados).</p>
<p>De modo a contornar este inconveniente, podemos utilizar o método <em>deepcopy</em> da <em>lib</em> nativa <a href="https://docs.python.org/2/library/copy.html">copy</a>. Agora, ao inicializarmos <strong>new_dict</strong>:</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">copy</span>
<span class="n">dict_3</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'a'</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="s1">'b'</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="s1">'c'</span><span class="p">:</span> <span class="p">{</span>
<span class="s1">'d'</span><span class="p">:</span> <span class="mi">5</span><span class="p">,</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="n">new_dict</span> <span class="o">=</span> <span class="n">copy</span><span class="o">.</span><span class="n">deepcopy</span><span class="p">(</span><span class="n">dict_3</span><span class="p">)</span>
</pre></div>
<p>O método <em>deepcopy</em> realiza uma cópia recursiva de cada elemento de <strong>dict_3</strong>, resolvendo nosso problema. Veja mais um exemplo:</p>
<div class="highlight"><pre><span></span><span class="o">>></span> <span class="n">new_dict</span><span class="p">[</span><span class="s1">'c'</span><span class="p">][</span><span class="s1">'d'</span><span class="p">]</span> <span class="o">=</span> <span class="mi">11</span>
<span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">new_dict</span><span class="p">[</span><span class="s1">'c'</span><span class="p">][</span><span class="s1">'d'</span><span class="p">])</span>
<span class="mi">11</span>
<span class="o">>></span> <span class="nb">print</span><span class="p">(</span><span class="n">dict_3</span><span class="p">[</span><span class="s1">'c'</span><span class="p">][</span><span class="s1">'d'</span><span class="p">])</span>
<span class="mi">5</span>
<span class="c1"># valor não foi alterado</span>
</pre></div>
<h2>Conclusão</h2>
<p>Este artigo tenta demonstrar de maneira simples a criação de <em>dicts</em>, utilizando os diversos recursos que a linguagem oferece bem como os prós e contras de cada abordagem. </p>
<h2>Referências</h2>
<p>Para mais detalhes e outros exemplos, deem uma olhada neste <em>post</em> do forum da Python Brasil <a href="https://groups.google.com/forum/#!topic/python-brasil/OhUqYQ32M7E">aqui</a>.</p>
<p>É isso pessoal. Obrigado por ler!</p>
2019-10-01T23:20:29+00:00
Michell Stuttgart
-
Humberto Rocha: Desbravando o pygame 5 - Movimento e Colisão
https://humberto.io/pt-br/blog/desbravando-o-pygame-5-movimento-e-colisao/
O movimento é uma característica que está presente na maioria dos jogos. Ao saltar entre plataformas, atirar contra a horda de inimigos, pilotar uma nave espacial e correr pelas estradas estamos exercendo movimento, interagindo com o ambiente do jogo, aplicando ações e causando reações.
Neste capítulo iremos conhecer os conceitos básicos de movimentação de objetos na tela e sua interação com outros elementos através da detecção de colisão.
Movimento Se você vem acompanhando esta série de postagens, teve um breve exemplo de movimentação na postagem sobre game loop, onde uma bola que se movimentava quicando pela tela foi implementada.
2019-09-10T00:00:00+00:00
-
Humberto Rocha: Publicando meu primeiro Jogo
https://humberto.io/pt-br/blog/publicando-meu-primeiro-jogo/
Jogos sempre me conectam com tecnologia desde o início.
Eu e meu pai montamos nosso primeiro computador (um Pentium 286) e a primeira coisa que eu me lembro de fazer, foi jogar os jogos de DOS como Prince of Persia e Lunar Lander. Eu aprendi vários comandos de CLI só para poder jogar os meus jogos favoritos.
A paixão por jogar e fazer jogos sempre me acompanhou como um hobby. I tenho uma série de posts sobre pygame neste blog onde eu passo pelos conceitos básicos de desenvolvimento de jogos tentando explicar para pessoas que estejam iniciando seu aprendizado na área.
2019-08-28T00:00:00+00:00
-
PythonClub: Tutorial Django 2.2
https://pythonclub.com.br/tutorial-django-2.html
<p>Este tutorial é baseado no <strong>Intro to Django</strong> que fica na parte de baixo da página <a href="https://www.djangoproject.com/start/">start</a> do Django project.</p>
<p>Até a data deste post o Django está na versão 2.2.2, e requer Python 3.</p>
<h2>O que você precisa?</h2>
<p>Python 3.6 ou superior, pip e virtualenv.</p>
<p>Considere que você tenha instalado Python 3.6 ou superior, <a href="https://pip.readthedocs.io/en/latest/">pip</a> e <a href="https://virtualenv.pypa.io/en/latest/">virtualenv</a>.</p>
<h2>Criando o ambiente</h2>
<p>Crie uma pasta com o nome <code>django2-pythonclub</code></p>
<div class="highlight"><pre><span></span>$ mkdir django2-pythonclub
$ <span class="nb">cd</span> django2-pythonclub
</pre></div>
<p>A partir de agora vamos considerar esta como a nossa pasta principal.</p>
<p>Considerando que você está usando <strong>Python 3</strong>, digite</p>
<div class="highlight"><pre><span></span><span class="err">python3 -m venv .venv</span>
</pre></div>
<p>Lembre-se de colocar esta pasta no seu <code>.gitignore</code>, caso esteja usando.</p>
<div class="highlight"><pre><span></span><span class="err">echo .venv >> .gitignore</span>
</pre></div>
<p>Depois ative o ambiente digitando</p>
<div class="highlight"><pre><span></span><span class="err">source .venv/bin/activate</span>
</pre></div>
<blockquote>
<p>Lembre-se, sempre quando você for mexer no projeto, tenha certeza de ter ativado o <code>virtualenv</code>, executando o comando <code>source .venv/bin/activate</code>. Você deve repetir esse comando toda a vez que você abrir um novo terminal.</p>
</blockquote>
<h2>Instalando Django 2.2.2</h2>
<p>Basta digitar</p>
<div class="highlight"><pre><span></span><span class="err">pip install django==2.2.2</span>
</pre></div>
<p>Dica: se você digitar <code>pip freeze</code> você verá a versão dos programas instalados.</p>
<p>É recomendável que você atualize a versão do <code>pip</code></p>
<div class="highlight"><pre><span></span><span class="err">pip install -U pip</span>
</pre></div>
<p>Se der erro então faça:</p>
<div class="highlight"><pre><span></span><span class="err">python -m pip install --upgrade pip</span>
</pre></div>
<h2>Instalando mais dependências</h2>
<p>Eu gosto de usar o <a href="https://django-extensions.readthedocs.io/en/latest/">django-extensions</a> e o <a href="https://github.com/jazzband/django-widget-tweaks">django-widget-tweaks</a>, então digite</p>
<div class="highlight"><pre><span></span><span class="err">pip install django-extensions django-widget-tweaks python-decouple</span>
</pre></div>
<p><strong>Importante:</strong> você precisa criar um arquivo <code>requirements.txt</code> para instalações futuras do projeto em outro lugar.</p>
<div class="highlight"><pre><span></span><span class="err">pip freeze > requirements.txt</span>
</pre></div>
<p>Este é o resultado do meu até o dia deste post:</p>
<div class="highlight"><pre><span></span><span class="p">(.</span><span class="n">venv</span><span class="p">):</span><span class="err">$</span> <span class="n">cat</span> <span class="n">requirements</span><span class="p">.</span><span class="n">txt</span>
<span class="n">django</span><span class="o">-</span><span class="n">extensions</span><span class="o">==</span><span class="mi">2</span><span class="p">.</span><span class="mi">1</span><span class="p">.</span><span class="mi">6</span>
<span class="n">django</span><span class="o">-</span><span class="n">widget</span><span class="o">-</span><span class="n">tweaks</span><span class="o">==</span><span class="mi">1</span><span class="p">.</span><span class="mi">4</span><span class="p">.</span><span class="mi">3</span>
<span class="n">python</span><span class="o">-</span><span class="n">decouple</span><span class="o">==</span><span class="mi">3</span><span class="p">.</span><span class="mi">1</span>
<span class="n">pytz</span><span class="o">==</span><span class="mi">2018</span><span class="p">.</span><span class="mi">9</span>
<span class="n">six</span><span class="o">==</span><span class="mi">1</span><span class="p">.</span><span class="mi">12</span><span class="p">.</span><span class="mi">0</span>
</pre></div>
<h2>Escondendo a SECRET_KEY e trabalhando com variáveis de ambiente</h2>
<p>É muito importante que você não deixe sua SECRET_KEY exposta. Então remova-o imediatamente do seu settings.py ANTES mesmo do primeiro commit. Espero que você esteja usando Git.</p>
<p>Vamos usar o <a href="https://github.com/henriquebastos/python-decouple">python-decouple</a> escrito por <a href="https://henriquebastos.net/">Henrique Bastos</a> para gerenciar nossas variáveis de ambiente. Repare que já instalamos ele logo acima.</p>
<p>Em seguida você vai precisar criar um arquivo <code>.env</code>, para isso rode o comando a seguir, ele vai criar uma pasta contrib e dentro dele colocar um arquivo <code>env_gen.py</code></p>
<div class="highlight"><pre><span></span><span class="err">if [ ! -d contrib ]; then mkdir contrib; fi; git clone https://gist.github.com/22626de522f5c045bc63acdb8fe67b24.git contrib/</span>
<span class="err">rm -rf contrib/.git/ # remova a pasta .git que está dentro de contrib.</span>
</pre></div>
<p>Em seguida rode</p>
<div class="highlight"><pre><span></span><span class="err">python contrib/env_gen.py</span>
</pre></div>
<p>que ele vai criar o arquivo <code>.env</code>.</p>
<p>Supondo que você está versionando seu código com Git, é importante que você escreva isso dentro do seu arquivo <code>.gitignore</code>, faça direto pelo terminal</p>
<div class="highlight"><pre><span></span><span class="err">echo .env >> .gitignore</span>
<span class="err">echo .venv >> .gitignore</span>
<span class="err">echo '*.sqlite3' >> .gitignore</span>
</pre></div>
<p>Pronto, agora você pode dar o primeiro commit.</p>
<h2>Criando o projeto e a App</h2>
<p>Para criar o projeto digite</p>
<div class="highlight"><pre><span></span>$ django-admin startproject myproject .
</pre></div>
<p>repare no ponto no final do comando, isto permite que o arquivo <code>manage.py</code> fique nesta mesma pasta <em>django2-pythonclub</em> .</p>
<p>Agora vamos criar a <em>app</em> <strong>bands</strong>, mas vamos deixar esta <em>app</em> dentro da pasta <em>myproject</em>. Então entre na pasta</p>
<div class="highlight"><pre><span></span>$ <span class="nb">cd</span> myproject
</pre></div>
<p>e digite</p>
<div class="highlight"><pre><span></span>$ python ../manage.py startapp bands
</pre></div>
<p>A intenção é que os arquivos tenham a seguinte hierarquia nas pastas:</p>
<div class="highlight"><pre><span></span><span class="err">.</span>
<span class="err">├── manage.py</span>
<span class="err">├── myproject</span>
<span class="err">│ ├── bands</span>
<span class="err">│ │ ├── admin.py</span>
<span class="err">│ │ ├── apps.py</span>
<span class="err">│ │ ├── models.py</span>
<span class="err">│ │ ├── tests.py</span>
<span class="err">│ │ └── views.py</span>
<span class="err">│ ├── settings.py</span>
<span class="err">│ ├── urls.py</span>
<span class="err">│ └── wsgi.py</span>
<span class="err">└── requirements.txt</span>
</pre></div>
<p>Agora permaneça sempre na pasta <code>django2-pythonclub</code></p>
<div class="highlight"><pre><span></span><span class="err">cd ..</span>
</pre></div>
<p>e digite</p>
<div class="highlight"><pre><span></span>$ python manage.py migrate
</pre></div>
<p>para criar a primeira <em>migração</em> (isto cria o banco de dados SQLite), e depois rode a aplicação com</p>
<div class="highlight"><pre><span></span>$ python manage.py runserver
</pre></div>
<p>e veja que a aplicação já está funcionando. Veja o endereço da url aqui</p>
<div class="highlight"><pre><span></span><span class="err">Django version 2.2.2, using settings 'myproject.settings'</span>
<span class="err">Starting development server at https://127.0.0.1:8000/</span>
<span class="err">Quit the server with CONTROL-C.</span>
</pre></div>
<h2>Editando settings.py</h2>
<p>Em <code>INSTALLED_APPS</code> acrescente as linhas abaixo.</p>
<div class="highlight"><pre><span></span><span class="n">INSTALLED_APPS</span> <span class="o">=</span> <span class="p">(</span>
<span class="o">...</span>
<span class="s1">'widget_tweaks'</span><span class="p">,</span>
<span class="s1">'django_extensions'</span><span class="p">,</span>
<span class="s1">'myproject.bands'</span><span class="p">,</span>
<span class="p">)</span>
</pre></div>
<p>E mude também o idioma.</p>
<p><code>LANGUAGE_CODE = 'pt-br'</code></p>
<p>E caso você queira o mesmo horário de Brasília-BR</p>
<p><code>TIME_ZONE = 'America/Sao_Paulo'</code></p>
<p>Já que falamos do python-decouple, precisamos de mais alguns ajustes</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">decouple</span> <span class="kn">import</span> <span class="n">config</span><span class="p">,</span> <span class="n">Csv</span>
<span class="c1"># SECURITY WARNING: keep the secret key used in production secret!</span>
<span class="n">SECRET_KEY</span> <span class="o">=</span> <span class="n">config</span><span class="p">(</span><span class="s1">'SECRET_KEY'</span><span class="p">)</span>
<span class="c1"># SECURITY WARNING: don't run with debug turned on in production!</span>
<span class="n">DEBUG</span> <span class="o">=</span> <span class="n">config</span><span class="p">(</span><span class="s1">'DEBUG'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="kc">False</span><span class="p">,</span> <span class="n">cast</span><span class="o">=</span><span class="nb">bool</span><span class="p">)</span>
<span class="n">ALLOWED_HOSTS</span> <span class="o">=</span> <span class="n">config</span><span class="p">(</span><span class="s1">'ALLOWED_HOSTS'</span><span class="p">,</span> <span class="n">default</span><span class="o">=</span><span class="p">[],</span> <span class="n">cast</span><span class="o">=</span><span class="n">Csv</span><span class="p">())</span>
</pre></div>
<p>Veja que é importante manter sua SECRET_KEY bem guardada (em outro lugar).</p>
<p>Então crie um arquivo <code>.env</code> e guarde sua SECRET_KEY dentro dele, exemplo:</p>
<div class="highlight"><pre><span></span><span class="err">SECRET_KEY=your_secret_key</span>
<span class="err">DEBUG=True</span>
<span class="err">ALLOWED_HOSTS=127.0.0.1,.localhost</span>
</pre></div>
<h2>Editando models.py</h2>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.db</span> <span class="kn">import</span> <span class="n">models</span>
<span class="kn">from</span> <span class="nn">django.urls</span> <span class="kn">import</span> <span class="n">reverse_lazy</span>
<span class="k">class</span> <span class="nc">Band</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="sd">"""A model of a rock band."""</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">200</span><span class="p">)</span>
<span class="n">can_rock</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="n">default</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">ordering</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'name'</span><span class="p">,)</span>
<span class="n">verbose_name</span> <span class="o">=</span> <span class="s1">'band'</span>
<span class="n">verbose_name_plural</span> <span class="o">=</span> <span class="s1">'bands'</span>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>
<span class="k">def</span> <span class="nf">get_absolute_url</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c1"># retorna a url no formato /bands/1/</span>
<span class="k">return</span> <span class="n">reverse_lazy</span><span class="p">(</span><span class="s1">'band_detail'</span><span class="p">,</span> <span class="n">kwargs</span><span class="o">=</span><span class="p">{</span><span class="s1">'pk'</span><span class="p">:</span> <span class="bp">self</span><span class="o">.</span><span class="n">pk</span><span class="p">})</span>
<span class="k">def</span> <span class="nf">get_members_count</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="c1"># count members by band</span>
<span class="c1"># conta os membros por banda</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">band</span><span class="o">.</span><span class="n">count</span><span class="p">()</span>
<span class="k">class</span> <span class="nc">Member</span><span class="p">(</span><span class="n">models</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="sd">"""A model of a rock band member."""</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="s2">"Member's name"</span><span class="p">,</span> <span class="n">max_length</span><span class="o">=</span><span class="mi">200</span><span class="p">)</span>
<span class="n">instrument</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">choices</span><span class="o">=</span><span class="p">(</span>
<span class="p">(</span><span class="s1">'g'</span><span class="p">,</span> <span class="s2">"Guitar"</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'b'</span><span class="p">,</span> <span class="s2">"Bass"</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'d'</span><span class="p">,</span> <span class="s2">"Drums"</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'v'</span><span class="p">,</span> <span class="s2">"Vocal"</span><span class="p">),</span>
<span class="p">(</span><span class="s1">'p'</span><span class="p">,</span> <span class="s2">"Piano"</span><span class="p">),</span>
<span class="p">),</span>
<span class="n">max_length</span><span class="o">=</span><span class="mi">1</span>
<span class="p">)</span>
<span class="n">band</span> <span class="o">=</span> <span class="n">models</span><span class="o">.</span><span class="n">ForeignKey</span><span class="p">(</span><span class="s2">"Band"</span><span class="p">,</span> <span class="n">related_name</span><span class="o">=</span><span class="s1">'band'</span><span class="p">,</span> <span class="n">on_delete</span><span class="o">=</span><span class="n">models</span><span class="o">.</span><span class="n">CASCADE</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">ordering</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'name'</span><span class="p">,)</span>
<span class="n">verbose_name</span> <span class="o">=</span> <span class="s1">'member'</span>
<span class="n">verbose_name_plural</span> <span class="o">=</span> <span class="s1">'members'</span>
<span class="k">def</span> <span class="fm">__str__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">name</span>
</pre></div>
<p>Tem algumas coisas que eu não estou explicando aqui para o tutorial ficar curto, mas uma coisa importante é que, como nós editamos o models.py vamos precisar criar um arquivo de migração do novo modelo. Para isso digite</p>
<div class="highlight"><pre><span></span><span class="err">python manage.py makemigrations</span>
<span class="err">python manage.py migrate</span>
</pre></div>
<p>O primeiro comando cria o arquivo de migração e o segundo o executa, criando as tabelas no banco de dados.</p>
<h2>Editando urls.py</h2>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.urls</span> <span class="kn">import</span> <span class="n">include</span><span class="p">,</span> <span class="n">path</span>
<span class="kn">from</span> <span class="nn">myproject.bands</span> <span class="kn">import</span> <span class="n">views</span> <span class="k">as</span> <span class="n">v</span>
<span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span>
<span class="n">app_name</span> <span class="o">=</span> <span class="s1">'bands'</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">path</span><span class="p">(</span><span class="s1">''</span><span class="p">,</span> <span class="n">v</span><span class="o">.</span><span class="n">home</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'home'</span><span class="p">),</span>
<span class="c1"># path('bands/', v.band_list, name='bands'),</span>
<span class="c1"># path('bands/<int:pk>/', v.band_detail, name='band_detail'),</span>
<span class="c1"># path('bandform/', v.BandCreate.as_view(), name='band_form'),</span>
<span class="c1"># path('memberform/', v.MemberCreate.as_view(), name='member_form'),</span>
<span class="c1"># path('contact/', v.band_contact, name='contact'),</span>
<span class="c1"># path('protected/', v.protected_view, name='protected'),</span>
<span class="c1"># path('accounts/login/', v.message),</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="p">]</span>
</pre></div>
<p>Obs: deixei as demais urls comentada porque precisa da função em views.py para que cada url funcione. Descomente cada url somente depois que você tiver definido a função em classe em views.py a seguir.</p>
<h2>Editando views.py</h2>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.shortcuts</span> <span class="kn">import</span> <span class="n">render</span>
<span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">HttpResponse</span>
<span class="kn">from</span> <span class="nn">django.contrib.auth.decorators</span> <span class="kn">import</span> <span class="n">login_required</span>
<span class="kn">from</span> <span class="nn">django.views.generic</span> <span class="kn">import</span> <span class="n">CreateView</span>
<span class="kn">from</span> <span class="nn">django.urls</span> <span class="kn">import</span> <span class="n">reverse_lazy</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Band</span><span class="p">,</span> <span class="n">Member</span>
<span class="c1"># from .forms import BandContactForm, BandForm, MemberForm</span>
</pre></div>
<p>Obs: Deixei a última linha comentada porque ainda não chegamos em forms.</p>
<p>A função a seguir retorna um <strong>HttpResponse</strong>, ou seja, uma mensagem simples no navegador.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">home</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="s1">'Welcome to the site!'</span><span class="p">)</span>
</pre></div>
<p>A próxima função (use uma ou outra) renderiza um template, uma página html no navegador.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">home</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="s1">'home.html'</span><span class="p">)</span>
</pre></div>
<p>A função <code>band_list</code> retorna todas as bandas.</p>
<p>Para fazer a <strong>busca</strong> por nome de banda usamos o comando <code>search = request.GET.get('search_box')</code>, onde <code>search_box</code> é o nome do campo no template <strong>band_list.html</strong>.</p>
<p>E os nomes são retornados a partir do comando <code>bands = bands.filter(name__icontains=search)</code>. Onde <code>icontains</code> procura um texto que contém a palavra, ou seja, você pode digitar o nome incompleto (ignora maiúsculo ou minúsculo).</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">band_list</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="sd">""" A view of all bands. """</span>
<span class="n">bands</span> <span class="o">=</span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">search</span> <span class="o">=</span> <span class="n">request</span><span class="o">.</span><span class="n">GET</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">'search_box'</span><span class="p">)</span>
<span class="k">if</span> <span class="n">search</span><span class="p">:</span>
<span class="n">bands</span> <span class="o">=</span> <span class="n">bands</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">name__icontains</span><span class="o">=</span><span class="n">search</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="s1">'bands/band_list.html'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'bands'</span><span class="p">:</span> <span class="n">bands</span><span class="p">})</span>
</pre></div>
<p>Em urls.py pode descomentar a linha a seguir:</p>
<div class="highlight"><pre><span></span><span class="n">path</span><span class="p">(</span><span class="s1">'bands/'</span><span class="p">,</span> <span class="n">v</span><span class="o">.</span><span class="n">band_list</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'bands'</span><span class="p">),</span>
</pre></div>
<p>A função <code>band_contact</code> mostra como tratar um formulário na view. Esta função requer <code>BandContactForm</code>, explicado em forms.py.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">band_contact</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="sd">""" A example of form """</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">'POST'</span><span class="p">:</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">BandContactForm</span><span class="p">(</span><span class="n">request</span><span class="o">.</span><span class="n">POST</span><span class="p">)</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">form</span> <span class="o">=</span> <span class="n">BandContactForm</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="s1">'bands/band_contact.html'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'form'</span><span class="p">:</span> <span class="n">form</span><span class="p">})</span>
</pre></div>
<p>Em urls.py pode descomentar a linha a seguir:</p>
<div class="highlight"><pre><span></span><span class="n">path</span><span class="p">(</span><span class="s1">'contact/'</span><span class="p">,</span> <span class="n">v</span><span class="o">.</span><span class="n">band_contact</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'contact'</span><span class="p">),</span>
</pre></div>
<p>A função <code>band_detail</code> retorna todos os membros de cada banda, usando o <code>pk</code> da banda junto com o comando <code>filter</code> em members.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">band_detail</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">pk</span><span class="p">):</span>
<span class="sd">""" A view of all members by bands. """</span>
<span class="n">band</span> <span class="o">=</span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="n">pk</span><span class="p">)</span>
<span class="n">members</span> <span class="o">=</span> <span class="n">Member</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span><span class="o">.</span><span class="n">filter</span><span class="p">(</span><span class="n">band</span><span class="o">=</span><span class="n">band</span><span class="p">)</span>
<span class="n">context</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'members'</span><span class="p">:</span> <span class="n">members</span><span class="p">,</span> <span class="s1">'band'</span><span class="p">:</span> <span class="n">band</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="s1">'bands/band_detail.html'</span><span class="p">,</span> <span class="n">context</span><span class="p">)</span>
</pre></div>
<p>Em urls.py pode descomentar a linha a seguir:</p>
<div class="highlight"><pre><span></span><span class="n">path</span><span class="p">(</span><span class="s1">'bands/<int:pk>/'</span><span class="p">,</span> <span class="n">v</span><span class="o">.</span><span class="n">band_detail</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'band_detail'</span><span class="p">),</span>
</pre></div>
<p><code>BandCreate</code> e <code>MemberCreate</code> usam o <a href="https://docs.djangoproject.com/en/2.2/ref/class-based-views/">Class Based View</a> para tratar formulário de uma forma mais simplificada usando a classe <code>CreateView</code>. O <code>reverse_lazy</code> serve para tratar a url de retorno de página.</p>
<p>As classes a seguir requerem <code>BandForm</code> e <code>MemberForm</code>, explicado em forms.py.</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">BandCreate</span><span class="p">(</span><span class="n">CreateView</span><span class="p">):</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Band</span>
<span class="n">form_class</span> <span class="o">=</span> <span class="n">BandForm</span>
<span class="n">template_name</span> <span class="o">=</span> <span class="s1">'bands/band_form.html'</span>
<span class="n">success_url</span> <span class="o">=</span> <span class="n">reverse_lazy</span><span class="p">(</span><span class="s1">'bands'</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">MemberCreate</span><span class="p">(</span><span class="n">CreateView</span><span class="p">):</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Member</span>
<span class="n">form_class</span> <span class="o">=</span> <span class="n">MemberForm</span>
<span class="n">template_name</span> <span class="o">=</span> <span class="s1">'bands/member_form.html'</span>
<span class="n">success_url</span> <span class="o">=</span> <span class="n">reverse_lazy</span><span class="p">(</span><span class="s1">'bands'</span><span class="p">)</span>
</pre></div>
<p>Em urls.py pode descomentar a linha a seguir:</p>
<div class="highlight"><pre><span></span><span class="n">path</span><span class="p">(</span><span class="s1">'bandform/'</span><span class="p">,</span> <span class="n">v</span><span class="o">.</span><span class="n">BandCreate</span><span class="o">.</span><span class="n">as_view</span><span class="p">(),</span> <span class="n">name</span><span class="o">=</span><span class="s1">'band_form'</span><span class="p">),</span>
<span class="n">path</span><span class="p">(</span><span class="s1">'memberform/'</span><span class="p">,</span> <span class="n">v</span><span class="o">.</span><span class="n">MemberCreate</span><span class="o">.</span><span class="n">as_view</span><span class="p">(),</span> <span class="n">name</span><span class="o">=</span><span class="s1">'member_form'</span><span class="p">),</span>
</pre></div>
<p>A próxima função requer que você entre numa página somente quando estiver logado.</p>
<p><code>[@login_required](https://docs.djangoproject.com/en/2.2/topics/auth/default/#the-login-required-decorator)</code> é um <strong>decorator</strong>.</p>
<p><code>login_url='/accounts/login/'</code> é página de erro, ou seja, quando o usuário não conseguiu logar.</p>
<p>E <code>render(request, 'bands/protected.html',...</code> é página de sucesso.</p>
<div class="highlight"><pre><span></span><span class="nd">@login_required</span><span class="p">(</span><span class="n">login_url</span><span class="o">=</span><span class="s1">'/accounts/login/'</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">protected_view</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="sd">""" A view that can only be accessed by logged-in users """</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="s1">'bands/protected.html'</span><span class="p">,</span> <span class="p">{</span><span class="s1">'current_user'</span><span class="p">:</span> <span class="n">request</span><span class="o">.</span><span class="n">user</span><span class="p">})</span>
</pre></div>
<p><code>HttpResponse</code> retorna uma mensagem simples no navegador sem a necessidade de um template.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">message</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="sd">""" Message if is not authenticated. Simple view! """</span>
<span class="k">return</span> <span class="n">HttpResponse</span><span class="p">(</span><span class="s1">'Access denied!'</span><span class="p">)</span>
</pre></div>
<p>Em urls.py pode descomentar a linha a seguir:</p>
<div class="highlight"><pre><span></span><span class="n">path</span><span class="p">(</span><span class="s1">'protected/'</span><span class="p">,</span> <span class="n">v</span><span class="o">.</span><span class="n">protected_view</span><span class="p">,</span> <span class="n">name</span><span class="o">=</span><span class="s1">'protected'</span><span class="p">),</span>
<span class="n">path</span><span class="p">(</span><span class="s1">'accounts/login/'</span><span class="p">,</span> <span class="n">v</span><span class="o">.</span><span class="n">message</span><span class="p">),</span>
</pre></div>
<h2>Comandos básicos do manage.py</h2>
<p>Para criar novas migrações com base nas alterações feitas nos seus modelos</p>
<p><code>$ python manage.py makemigrations bands</code></p>
<p>Obs: talvez dê erro porque está faltando coisas de forms.py, explicado mais abaixo.</p>
<p>Para aplicar as migrações</p>
<p><code>$ python manage.py migrate</code></p>
<p>Para criar um usuário e senha para o admin</p>
<p><code>$ python manage.py createsuperuser</code></p>
<p>Para rodar a aplicação localmente</p>
<p><code>$ python manage.py runserver</code></p>
<p>Após criar um super usuário você pode entrar em localhost:8000/admin</p>
<p>Obs: Se você entrar agora em localhost:8000 vai faltar o template home.html. Explicado mais abaixo.</p>
<h2>shell_plus</h2>
<p>É o <strong>interpretador interativo do python</strong> rodando <strong>via terminal</strong> direto na aplicação do django.</p>
<p>Com o comando a seguir abrimos o shell do Django.</p>
<p><code>$ python manage.py shell</code></p>
<p>Mas se você está usando o django-extensions (mostrei como configurá-lo no settings.py), então basta digitar</p>
<p><code>$ python manage.py shell_plus</code></p>
<p>Veja a seguir como inserir dados direto pelo shell.</p>
<div class="highlight"><pre><span></span><span class="o">>>></span> <span class="kn">from</span> <span class="nn">myproject.bands.models</span> <span class="kn">import</span> <span class="n">Band</span><span class="p">,</span> <span class="n">Member</span>
<span class="o">>>></span> <span class="c1"># Com django-extensions não precisa fazer o import</span>
<span class="o">>>></span> <span class="c1"># criando o objeto e salvando</span>
<span class="o">>>></span> <span class="n">band</span> <span class="o">=</span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Metallica'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">band</span><span class="o">.</span><span class="n">name</span>
<span class="o">>>></span> <span class="n">band</span><span class="o">.</span><span class="n">can_rock</span>
<span class="o">>>></span> <span class="n">band</span><span class="o">.</span><span class="n">id</span>
<span class="o">>>></span> <span class="c1"># criando uma instancia da banda a partir do id</span>
<span class="o">>>></span> <span class="n">b</span> <span class="o">=</span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">band</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
<span class="o">>>></span> <span class="c1"># criando uma instancia do Membro e associando o id da banda a ela</span>
<span class="o">>>></span> <span class="n">m</span> <span class="o">=</span> <span class="n">Member</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'James Hetfield'</span><span class="p">,</span> <span class="n">instrument</span><span class="o">=</span><span class="s1">'b'</span><span class="p">,</span> <span class="n">band</span><span class="o">=</span><span class="n">b</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">m</span><span class="o">.</span><span class="n">name</span>
<span class="o">>>></span> <span class="c1"># retornando o instrumento</span>
<span class="o">>>></span> <span class="n">m</span><span class="o">.</span><span class="n">instrument</span>
<span class="o">>>></span> <span class="n">m</span><span class="o">.</span><span class="n">get_instrument_display</span><span class="p">()</span>
<span class="o">>>></span> <span class="n">m</span><span class="o">.</span><span class="n">band</span>
<span class="o">>>></span> <span class="c1"># salvando</span>
<span class="o">>>></span> <span class="n">m</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="o">>>></span> <span class="c1"># listando todas as bandas</span>
<span class="o">>>></span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="o">>>></span> <span class="c1"># listando todos os membros</span>
<span class="o">>>></span> <span class="n">Member</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="o">>>></span> <span class="c1"># criando mais uma banda</span>
<span class="o">>>></span> <span class="n">band</span> <span class="o">=</span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'The Beatles'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">band</span> <span class="o">=</span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'The Beatles'</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">band</span><span class="o">.</span><span class="n">id</span>
<span class="o">>>></span> <span class="n">b</span> <span class="o">=</span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="nb">id</span><span class="o">=</span><span class="n">band</span><span class="o">.</span><span class="n">id</span><span class="p">)</span>
<span class="o">>>></span> <span class="c1"># criando mais um membro</span>
<span class="o">>>></span> <span class="n">m</span> <span class="o">=</span> <span class="n">Member</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'John Lennon'</span><span class="p">,</span> <span class="n">instrument</span><span class="o">=</span><span class="s1">'v'</span><span class="p">,</span> <span class="n">band</span><span class="o">=</span><span class="n">b</span><span class="p">)</span>
<span class="o">>>></span> <span class="n">m</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="o">>>></span> <span class="c1"># listando tudo novamente</span>
<span class="o">>>></span> <span class="n">Band</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="o">>>></span> <span class="n">Member</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="o">>>></span> <span class="n">exit</span><span class="p">()</span>
</pre></div>
<h2>Criando os templates</h2>
<p>Você pode criar os templates com os comandos a seguir...</p>
<div class="highlight"><pre><span></span>$ mkdir -p myproject/bands/templates/bands
$ touch myproject/bands/templates/<span class="o">{</span>menu,base,home<span class="o">}</span>.html
$ touch myproject/bands/templates/bands/<span class="o">{</span>band_list,band_detail,band_form,band_contact,member_form,protected<span class="o">}</span>.html
</pre></div>
<p>... ou pegar os templates já prontos direto do Github.</p>
<div class="highlight"><pre><span></span><span class="err">mkdir -p myproject/bands/templates/bands</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/base.html -P myproject/bands/templates/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/home.html -P myproject/bands/templates/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/menu.html -P myproject/bands/templates/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_contact.html -P myproject/bands/templates/bands/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_detail.html -P myproject/bands/templates/bands/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_form.html -P myproject/bands/templates/bands/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/band_list.html -P myproject/bands/templates/bands/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/member_form.html -P myproject/bands/templates/bands/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/myproject/bands/templates/bands/protected.html -P myproject/bands/templates/bands/</span>
</pre></div>
<h2>forms.py</h2>
<p><code>$ touch myproject/bands/forms.py</code></p>
<p>Edite o forms.py.</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django</span> <span class="kn">import</span> <span class="n">forms</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Band</span><span class="p">,</span> <span class="n">Member</span>
<span class="k">class</span> <span class="nc">BandContactForm</span><span class="p">(</span><span class="n">forms</span><span class="o">.</span><span class="n">Form</span><span class="p">):</span>
<span class="n">subject</span> <span class="o">=</span> <span class="n">forms</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">max_length</span><span class="o">=</span><span class="mi">100</span><span class="p">)</span>
<span class="n">message</span> <span class="o">=</span> <span class="n">forms</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">widget</span><span class="o">=</span><span class="n">forms</span><span class="o">.</span><span class="n">Textarea</span><span class="p">)</span>
<span class="n">sender</span> <span class="o">=</span> <span class="n">forms</span><span class="o">.</span><span class="n">EmailField</span><span class="p">()</span>
<span class="n">cc_myself</span> <span class="o">=</span> <span class="n">forms</span><span class="o">.</span><span class="n">BooleanField</span><span class="p">(</span><span class="n">required</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">BandForm</span><span class="p">(</span><span class="n">forms</span><span class="o">.</span><span class="n">ModelForm</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Band</span>
<span class="n">fields</span> <span class="o">=</span> <span class="s1">'__all__'</span>
<span class="k">class</span> <span class="nc">MemberForm</span><span class="p">(</span><span class="n">forms</span><span class="o">.</span><span class="n">ModelForm</span><span class="p">):</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="n">model</span> <span class="o">=</span> <span class="n">Member</span>
<span class="n">fields</span> <span class="o">=</span> <span class="s1">'__all__'</span>
</pre></div>
<p>Lembra que eu deixei o código comentado em views.py?</p>
<p>Descomente ele por favor</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">.forms</span> <span class="kn">import</span> <span class="n">BandContactForm</span><span class="p">,</span> <span class="n">BandForm</span><span class="p">,</span> <span class="n">MemberForm</span>
</pre></div>
<h2>admin.py</h2>
<p>Criamos uma customização para o admin onde em members aparece um filtro por bandas.</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.contrib</span> <span class="kn">import</span> <span class="n">admin</span>
<span class="kn">from</span> <span class="nn">.models</span> <span class="kn">import</span> <span class="n">Band</span><span class="p">,</span> <span class="n">Member</span>
<span class="k">class</span> <span class="nc">MemberAdmin</span><span class="p">(</span><span class="n">admin</span><span class="o">.</span><span class="n">ModelAdmin</span><span class="p">):</span>
<span class="sd">"""Customize the look of the auto-generated admin for the Member model."""</span>
<span class="n">list_display</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'name'</span><span class="p">,</span> <span class="s1">'instrument'</span><span class="p">)</span>
<span class="n">list_filter</span> <span class="o">=</span> <span class="p">(</span><span class="s1">'band'</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">register</span><span class="p">(</span><span class="n">Band</span><span class="p">)</span> <span class="c1"># Use the default options</span>
<span class="n">admin</span><span class="o">.</span><span class="n">site</span><span class="o">.</span><span class="n">register</span><span class="p">(</span><span class="n">Member</span><span class="p">,</span> <span class="n">MemberAdmin</span><span class="p">)</span> <span class="c1"># Use the customized options</span>
</pre></div>
<h2>Carregando dados de um CSV</h2>
<p>Vamos baixar alguns arquivos para criar os dados no banco a partir de um CSV.</p>
<div class="highlight"><pre><span></span><span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/create_data.py</span>
<span class="err">mkdir fix</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/fix/bands.csv -P fix/</span>
<span class="err">wget https://raw.githubusercontent.com/rg3915/django2-pythonclub/master/fix/members.csv -P fix/</span>
</pre></div>
<p>Estando na pasta principal, rode o comando</p>
<div class="highlight"><pre><span></span><span class="err">python create_data.py</span>
</pre></div>
<p>que ele vai carregar alguns dados pra você.</p>
<p>Veja o código de <a href="https://github.com/rg3915/django2-pythonclub/blob/master/create_data.py">create_data.py</a>.</p>
<p>Veja o código completo em https://github.com/rg3915/django2-pythonclub</p>
<p><code>git clone https://github.com/rg3915/django2-pythonclub.git</code></p>
2019-06-25T01:00:00+00:00
Regis da Silva
-
Gabbleblotchits: Scientific Rust #rust2019
https://blog.luizirber.org/2019/01/05/rust-2019/
<p>The Rust community requested feedback last year for where the language should go
in 2018, and now they are running it again for 2019.
Last year I was too new in Rust to organize a blog post,
but after an year using it I feel more comfortable writing this!</p>
<p>(Check my previous post about <a href="https://blog.luizirber.org/2018/08/23/sourmash-rust/">replacing the C++ core in sourmash with Rust</a> for more details on how I spend my year in Rust).</p>
<h2>What counts as "scientific Rust"?</h2>
<p>Anything that involves doing science using computers counts as
scientific programming. It includes from embedded software
<a href="https://www.youtube.com/watch?v=y5Yd3FC-kh8">running on satellites</a> to climate models
running in supercomputers, from shell scripts running tools in a pipeline to
data analysis using notebooks.</p>
<p>It also makes the discussion harder, because it's too general! But it is very
important to keep in mind, because scientists are not your regular user: they
are highly qualified in their field of expertise, and they are also pushing the
boundaries of what we know (and this might need flexibility in their tools).</p>
<p>In this post I will be focusing more in two areas: array computing (what most
people consider 'scientific programming' to be) and "data structures".</p>
<h3>Array computing</h3>
<p>This one is booming in the last couple of years due to industry interest in data
sciences and deep learning (where they will talk about tensors instead of arrays),
and has its roots in models running in supercomputers (a field where Fortran is
still king!). Data tends to be quite regular (representable with matrices) and
amenable to parallel processing.</p>
<p>A good example is the SciPy stack in Python, built on top of NumPy and SciPy.
The adoption of the SciPy stack (both in academia and industry) is staggering,
and many <a href="https://github.com/cupy/cupy">alternative implementations</a> try to provide a NumPy-like API to try to
capture its mindshare.</p>
<p>This is the compute-intensive side science (be it CPU or GPU/TPU), and also the kind
of data that pushed CPU evolution and is still very important in defining policy
in scientific computing funding (see countries competing for the largest
supercomputers and measuring performance in floating point operations per second).</p>
<h3>Data structures for efficient data representation</h3>
<p>For data that is not so regular the situation is a bit different. I'll use
bioinformatics as an example: the data we get out of nucleotide sequencers is usually
represented by long strings (of ACGT), and algorithms will do a lot of string processing
(be it building string-overlap graphs for assembly, or searching for substrings
in large collections). This is only one example: there are many analyses that
will work with other types of data, and most of them don't have a
universal data representation as in the array computing case.</p>
<p>This is the memory-intensive science, and it's hard to measure performance in
floating point operations because... most of the time you're not even using
floating point numbers. It also suffers from limited data locality (which is
almost a prerequisite for compute-intensive performance).</p>
<h2>High performance core, interactive API</h2>
<p>There is something common in both cases: while performance-intensive
code is implemented in C/C++/Fortran, users usually interact with the API from
other languages (especially Python or R) because it's faster to iterate and
explore the data, and many of the tools already available in these languages are
very helpful for these tasks (think Jupyter/pandas or RStudio/tidyverse).
These languages are used to define the computation, but it is a lower-level core
library that drives it (NumPy or Tensorflow follow this idea, for example).</p>
<h2>How to make Rust better for science?</h2>
<p>The biggest barrier to learning Rust is the ownership model, and while we can
agree it is an important feature it is also quite daunting for newcomers,
especially if they don't have previous programming experience and exposure to
what bugs are being prevented. I don't see it being the first language we teach
to scientists any time soon, because the majority of scientists are not system
programmers, and have very different expectations for a programming language.
That doesn't mean that they can't benefit from Rust!</p>
<p>Rust is already great for building the performance-intensive parts,
and thanks to Cargo it is also a better alternative for sharing this code around,
since they tend to get 'stuck' inside Python or R packages.
And the 'easy' approach of vendoring C/C++ instead of having packages make it
hard to keep track of changes and doesn't encourage reusable code.</p>
<p>And, of course, if this code is Rust instead of C/C++ it also means that Rust
users can use them directly, without depending on the other languages. Seems
like a good way to bootstrap a scientific community in Rust =]</p>
<h2>What I would like to see in 2019?</h2>
<h3>An attribute proc-macro like <code>#[wasm_bindgen]</code> but for FFI</h3>
<p>While FFI is an integral part of Rust goals (interoperability with C/C++), I
have serious envy of the structure and tooling developed for WebAssembly! (Even
more now that it works in stable too)</p>
<p>We already have <code>#[no_mangle]</code> and <code>pub extern "C"</code>, but they are quite
low-level. I would love to see something closer to what wasm-bindgen does,
and define some traits (like <a href="https://rustwasm.github.io/wasm-bindgen/api/wasm_bindgen/convert/trait.IntoWasmAbi.html"><code>IntoWasmAbi</code></a>) to make it easier to
pass more complex data types through the FFI.</p>
<p>I know it's not that simple, and there are different design restrictions than
WebAssembly to take into account... The point here is not having the perfect
solution for all use cases, but something that serves as an entry point and helps
to deal with the complexity while you're still figuring out all the quirks and
traps of FFI. You can still fallback and have more control using the lower-level
options when the need rises.</p>
<h3>More -sys and Rust-like crates for interoperability with the larger ecosystems</h3>
<p>There are new projects bringing more interoperability to <a href="https://arrow.apache.org/">dataframes</a> and <a href="https://xnd.io/">tensors</a>.
While this ship has already sailed and they are implemented in C/C++,
it would be great to be a first-class citizen,
and not reinvent the wheel.
(Note: the arrow project already have pretty good Rust support!)</p>
<p>In my own corner (bioinformatics), the <a href="https://github.com/rust-bio">Rust-bio community</a> is doing a
great job of wrapping <a href="https://github.com/rust-bio/rust-htslib">useful libraries in C/C++</a> and exposing them to
Rust (and also a shout-out to 10X Genomics for doing this work for
<a href="https://github.com/10XGenomics/rust-bwa">other tools</a> while also contributing to Rust-bio!).</p>
<h3>More (bioinformatics) tools using Rust!</h3>
<p>We already have great examples like <a href="https://github.com/onecodex/finch-rs">finch</a> and <a href="https://github.com/natir/yacrd">yacrd</a>,
since Rust is great for single binary distribution of programs.
And with bioinformatics focusing so much in independent tools chained together in workflows,
I think we can start convincing people to try it out =]</p>
<h3>A place to find other scientists?</h3>
<p>Another idea is to draw inspiration from <a href="https://ropensci.org/about/">rOpenSci</a> and have a Rust equivalent,
where people can get feedback about their projects and how to better integrate it with other crates.
This is quite close to the working group idea,
but I think it would serve more as a gateway to other groups,
more focused on developing entry-level docs and bringing more scientists to the
community.</p>
<h2>Final words</h2>
<p>In the end, I feel like this post ended up turning into my 'wishful TODO list'
for 2019, but I would love to find more people sharing these goals (or willing
to take any of this and just run with it, I do have a PhD to finish! =P)</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://social.lasanha.org/@luizirber/101367104235280253">Thread on Mastodon</a></li>
<li><a href="https://twitter.com/luizirber/status/1081729107170193408">Thread on Twitter</a></li>
</ul>
2019-01-05T19:00:00+00:00
luizirber
-
PythonClub: Algoritmos de Ordenação
https://pythonclub.com.br/algoritmos-ordenacao.html
<p>Fala pessoal, tudo bom?</p>
<p>Nos vídeos abaixo, vamos aprender como implementar alguns dos algoritmos de ordenação usando Python.</p>
<div class="section" id="bubble-sort">
<h2>Bubble Sort</h2>
<p>Como o algoritmo funciona: Como implementar o algoritmo usando Python: <a class="reference external" href="https://www.youtube.com/watch?v=Doy64STkwlI&list=PLvo_Yb_myrNBhIdq8qqtNSDFtnBfsKL2r&t=0s&index=3">https://www.youtube.com/watch?v=Doy64STkwlI</a>.</p>
<div class="youtube youtube-16x9"></div><p>Como implementar o algoritmo usando Python: <a class="reference external" href="https://www.youtube.com/watch?v=B0DFF0fE4rk&index=3&list=PLvo_Yb_myrNBhIdq8qqtNSDFtnBfsKL2r">https://www.youtube.com/watch?v=B0DFF0fE4rk</a>.</p>
<div class="youtube youtube-16x9"></div><p>Código do algoritmo</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">sort</span><span class="p">(</span><span class="n">array</span><span class="p">):</span>
<span class="k">for</span> <span class="n">final</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">array</span><span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="o">-</span><span class="mi">1</span><span class="p">):</span>
<span class="n">exchanging</span> <span class="o">=</span> <span class="kc">False</span>
<span class="k">for</span> <span class="n">current</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="n">final</span> <span class="o">-</span> <span class="mi">1</span><span class="p">):</span>
<span class="k">if</span> <span class="n">array</span><span class="p">[</span><span class="n">current</span><span class="p">]</span> <span class="o">></span> <span class="n">array</span><span class="p">[</span><span class="n">current</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]:</span>
<span class="n">array</span><span class="p">[</span><span class="n">current</span> <span class="o">+</span> <span class="mi">1</span><span class="p">],</span> <span class="n">array</span><span class="p">[</span><span class="n">current</span><span class="p">]</span> <span class="o">=</span> <span class="n">array</span><span class="p">[</span><span class="n">current</span><span class="p">],</span> <span class="n">array</span><span class="p">[</span><span class="n">current</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span>
<span class="n">exchanging</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">exchanging</span><span class="p">:</span>
<span class="k">break</span>
</pre></div>
</div>
<div class="section" id="selection-sort">
<h2>Selection Sort</h2>
<p>Como o algoritmo funciona: Como implementar o algoritmo usando Python: <a class="reference external" href="https://www.youtube.com/watch?v=vHxtP9BC-AA&list=PLvo_Yb_myrNBhIdq8qqtNSDFtnBfsKL2r&index=4">https://www.youtube.com/watch?v=vHxtP9BC-AA</a>.</p>
<div class="youtube youtube-16x9"></div><p>Como implementar o algoritmo usando Python: <a class="reference external" href="https://www.youtube.com/watch?v=0ORfCwwhF_I&index=5&list=PLvo_Yb_myrNBhIdq8qqtNSDFtnBfsKL2r&index=5">https://www.youtube.com/watch?v=0ORfCwwhF_I</a>.</p>
<div class="youtube youtube-16x9"></div><p>Código do algoritmo</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">sort</span><span class="p">(</span><span class="n">array</span><span class="p">):</span>
<span class="k">for</span> <span class="n">index</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="nb">len</span><span class="p">(</span><span class="n">array</span><span class="p">)):</span>
<span class="n">min_index</span> <span class="o">=</span> <span class="n">index</span>
<span class="k">for</span> <span class="n">right</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">index</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">array</span><span class="p">)):</span>
<span class="k">if</span> <span class="n">array</span><span class="p">[</span><span class="n">right</span><span class="p">]</span> <span class="o"><</span> <span class="n">array</span><span class="p">[</span><span class="n">min_index</span><span class="p">]:</span>
<span class="n">min_index</span> <span class="o">=</span> <span class="n">right</span>
<span class="n">array</span><span class="p">[</span><span class="n">index</span><span class="p">],</span> <span class="n">array</span><span class="p">[</span><span class="n">min_index</span><span class="p">]</span> <span class="o">=</span> <span class="n">array</span><span class="p">[</span><span class="n">min_index</span><span class="p">],</span> <span class="n">array</span><span class="p">[</span><span class="n">index</span><span class="p">]</span>
</pre></div>
</div>
<div class="section" id="insertion-sort">
<h2>Insertion Sort</h2>
<p>Como o algoritmo funciona: Como implementar o algoritmo usando Python: <a class="reference external" href="https://www.youtube.com/watch?v=O_E-Lj5HuRU&list=PLvo_Yb_myrNBhIdq8qqtNSDFtnBfsKL2r&t=0s&index=6">https://www.youtube.com/watch?v=O_E-Lj5HuRU</a>.</p>
<div class="youtube youtube-16x9"></div><p>Como implementar o algoritmo usando Python: <a class="reference external" href="https://www.youtube.com/watch?v=Sy_Z1pqMgko&index=7&list=PLvo_Yb_myrNBhIdq8qqtNSDFtnBfsKL2r">https://www.youtube.com/watch?v=Sy_Z1pqMgko</a>.</p>
<div class="youtube youtube-16x9"></div><p>Código do algoritmo</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">sort</span><span class="p">(</span><span class="n">array</span><span class="p">):</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">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">array</span><span class="p">)):</span>
<span class="n">current_element</span> <span class="o">=</span> <span class="n">array</span><span class="p">[</span><span class="n">p</span><span class="p">]</span>
<span class="k">while</span> <span class="n">p</span> <span class="o">></span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">array</span><span class="p">[</span><span class="n">p</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span> <span class="o">></span> <span class="n">current_element</span><span class="p">:</span>
<span class="n">array</span><span class="p">[</span><span class="n">p</span><span class="p">]</span> <span class="o">=</span> <span class="n">array</span><span class="p">[</span><span class="n">p</span> <span class="o">-</span> <span class="mi">1</span><span class="p">]</span>
<span class="n">p</span> <span class="o">-=</span> <span class="mi">1</span>
<span class="n">array</span><span class="p">[</span><span class="n">p</span><span class="p">]</span> <span class="o">=</span> <span class="n">current_element</span>
</pre></div>
</div>
<div class="section" id="merge-sort">
<h2>Merge Sort</h2>
<p>Como o algoritmo funciona: Como implementar o algoritmo usando Python: <a class="reference external" href="https://www.youtube.com/watch?v=Lnww0ibU0XM&list=PLvo_Yb_myrNBhIdq8qqtNSDFtnBfsKL2r&t=0s&index=8">https://www.youtube.com/watch?v=Lnww0ibU0XM</a>.</p>
<div class="youtube youtube-16x9"></div><p>Como implementar o algoritmo usando Python - Parte I: <a class="reference external" href="https://www.youtube.com/watch?v=cXJHETlYyVk&index=9&list=PLvo_Yb_myrNBhIdq8qqtNSDFtnBfsKL2r">https://www.youtube.com/watch?v=cXJHETlYyVk</a>.</p>
<div class="youtube youtube-16x9"></div><p>Código do algoritmo</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">sort</span><span class="p">(</span><span class="n">array</span><span class="p">):</span>
<span class="n">sort_half</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">array</span><span class="p">)</span> <span class="o">-</span> <span class="mi">1</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">sort_half</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">):</span>
<span class="k">if</span> <span class="n">start</span> <span class="o">>=</span> <span class="n">end</span><span class="p">:</span>
<span class="k">return</span>
<span class="n">middle</span> <span class="o">=</span> <span class="p">(</span><span class="n">start</span> <span class="o">+</span> <span class="n">end</span><span class="p">)</span> <span class="o">//</span> <span class="mi">2</span>
<span class="n">sort_half</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">middle</span><span class="p">)</span>
<span class="n">sort_half</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">middle</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">end</span><span class="p">)</span>
<span class="n">merge</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">merge</span><span class="p">(</span><span class="n">array</span><span class="p">,</span> <span class="n">start</span><span class="p">,</span> <span class="n">end</span><span class="p">):</span>
<span class="n">array</span><span class="p">[</span><span class="n">start</span><span class="p">:</span> <span class="n">end</span> <span class="o">+</span> <span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="nb">sorted</span><span class="p">(</span><span class="n">array</span><span class="p">[</span><span class="n">start</span><span class="p">:</span> <span class="n">end</span> <span class="o">+</span> <span class="mi">1</span><span class="p">])</span>
</pre></div>
</div>
2018-11-29T15:10:00+00:00
Lucas Magnum
-
PythonClub: Trabalhando com operadores ternários
https://pythonclub.com.br/trabalhando-com-operadores-ternarios.html
<p>Quando estamos escrevendo um código qualquer, possivelmente
a expressão que mais utilizamos é o <code>if</code>. Para qualquer
tarefas que buscamos automatizar ou problemas que buscamos
resolver, sempre acabamos caindo em lógicas como "Se isso
acontecer, então faça aquilo, senão faça aquele outro...".</p>
<p>Quando estamos falando de ações a serem executadas, pessoalmente
gosto da forma com que o código fica organizado em python quando
usamos este tipo de condições, por exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">if</span> <span class="n">vencer_o_thanos</span><span class="p">:</span>
<span class="n">restaurar_a_paz</span><span class="p">()</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">foo</span><span class="p">()</span>
</pre></div>
<p>Graças a indentação e ao espaçamento, vemos onde onde começa e/ou
termina o bloco executado caso a varável <code>vencer_o_thanos</code> seja
<code>True</code>. Quanto mais <code>if</code>'s você aninhar, mais bonito seu
código fica e em momento algum o mesmo se torna mais confuso
(ao menos, não deveria se tornar). Entretanto, sempre fico
extremamente incomodado quando tenho de escrever um bloco apenas
marcar uma variável, como por exemplo:</p>
<div class="highlight"><pre><span></span><span class="k">if</span> <span class="n">vencer_o_thanos</span><span class="p">:</span>
<span class="n">paz</span> <span class="o">=</span> <span class="kc">True</span>
<span class="k">else</span><span class="p">:</span>
<span class="n">paz</span> <span class="o">=</span> <span class="kc">False</span>
</pre></div>
<p>Por isso, para trabalhar com variáveis que possuem um valor condicional,
gosto sempre de trabalhar com expressões condicionais, ou como costumam
ser chamadas, <strong>operadores ternários</strong>.</p>
<p>Operadores ternários são todos os operadores que podem receber três
operandos. Como as expressões condicionais costumam ser os operadores
ternários mais populares nas linguagens em que aparecem, acabamos por
associar estes nomes e considerar que são a mesma coisa. Cuidado ao tirar
este tipo de conclusão, mesmo que toda vogal esteja no alfabeto, o
alfabeto não é composto apenas por vogais.</p>
<p>A estrutura de uma expressão condicional é algo bem simples, veja só:</p>
<div class="highlight"><pre><span></span><span class="n">paz</span> <span class="o">=</span> <span class="kc">True</span> <span class="k">if</span> <span class="n">vencer_o_thanos</span> <span class="k">else</span> <span class="kc">False</span>
<span class="n">tipo_de_x</span> <span class="o">=</span> <span class="s2">"Par"</span> <span class="k">if</span> <span class="n">x</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">0</span> <span class="k">else</span> <span class="s2">"impar"</span>
</pre></div>
<p>Resumidamente, teremos <strong>um valor seguido de uma condição e por fim seu
valor caso a condição seja falsa</strong>. Pessoalmente acredito que apesar de um
pouco diferente, essa forma de escrita para casos como o exemplificado acima
é muito mais clara, mais explicita.</p>
<p>Se você fizer uma tradução literal das booleanas utilizadas no primeiro exemplo,
lerá algo como <code>paz é verdadeira caso vencer_o_thanos, caso contrário é Falsa.</code>
já o segundo exemplo fica mais claro ainda, pois lemos algo como
<code>tipo_de_x é par caso o resto da divisão de x por 2 seja 0, se não, tipo_de_x é impar.</code>.</p>
<p>Interpretar código dessa forma pode ser estranho para um programador. Interpretar
uma abertura de chave ou uma indentação já é algo mas natural. Todavia, para aqueles
que estão começando, o raciocínio ocorre de forma muito mais parecida com a descrita
acima. Espero que tenham gostado do texto e que esse conhecimento lhes seja útil.</p>
2018-10-06T12:21:00+00:00
Vitor Hugo de Oliveira Vargas
-
Gabbleblotchits: What open science is about
https://blog.luizirber.org/2018/09/24/open-science/
<p>Today I got a pleasant surprise: <a href="https://www.olgabotvinnik.com/">Olga Botvinnik</a> posted on <a href="https://twitter.com/olgabot/status/1044292704513839104">Twitter</a>
about a <a href="https://github.com/czbiohub/kmer-hashing/blob/olgabot/search-compare-ignore-abundance/figures/presentations/2018-09-24_beyond_the_cell_atlas_poster/2018-09-24_Beyond_the_Cell_Atlas.pdf">poster</a> she is presenting at the Beyond the Cell Atlas conference
and she name-dropped a bunch of people that helped her. The cool thing? They
are all open source developers, and Olga interacted thru GitHub to <a href="https://github.com/dib-lab/sourmash/pull/543">ask</a> <a href="https://github.com/dib-lab/sourmash/issues/545">for</a> <a href="https://github.com/betteridiot/bamnostic/issues/15">features</a>,
<a href="https://github.com/betteridiot/bamnostic/issues/20">report bugs</a> and even <a href="https://github.com/dib-lab/sourmash/pull/543">submit</a> <a href="https://github.com/dib-lab/sourmash/pull/539">pull</a> <a href="https://github.com/dib-lab/sourmash/pull/529">requests</a>.</p>
<p>That's what open science is about: collaboration, good practices, and in the end coming up
with something that is larger than each individual piece. Now sourmash is better,
bamnostic is better, reflow is better. I would like to see this becoming more and
more common =]</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://social.lasanha.org/@luizirber/100783375923088450">Thread on Mastodon</a></li>
<li><a href="https://twitter.com/luizirber/status/1044369382233665536">Thread on Twitter</a></li>
</ul>
2018-09-24T20:00:00+00:00
luizirber
-
Gabbleblotchits: New crate: nthash
https://blog.luizirber.org/2018/09/13/nthash/
<p>A quick announcement: I wrote a <a href="https://github.com/luizirber/nthash">Rust implementation</a> of <a href="https://github.com/bcgsc/ntHash">ntHash</a> and published
it in <a href="https://crates.io/crates/nthash">crates.io</a>. It implements an <code>Iterator</code> to take advantage of the
rolling properties of <code>ntHash</code> which make it so useful in bioinformatics (where
we work a lot with sliding windows over sequences).</p>
<p>It's a pretty small crate, and probably was a better project to learn Rust than
doing a <a href="https://blog.luizirber.org/2018/08/23/sourmash-rust/">sourmash implementation</a> because it doesn't involve gnarly FFI
issues. I also put <a href="https://github.com/luizirber/nthash/blob/d0c16d7deb0a78b8aeb29090db91bba954c14fe8/src/lib.rs#L91">some docs</a>, <a href="https://github.com/luizirber/nthash/blob/d0c16d7deb0a78b8aeb29090db91bba954c14fe8/benches/nthash.rs#L11">benchmarks</a> using <a href="https://japaric.github.io/criterion.rs/">criterion</a>,
and even an <a href="https://github.com/luizirber/nthash/blob/d0c16d7deb0a78b8aeb29090db91bba954c14fe8/tests/nthash.rs#L80">oracle property-based test</a> with <a href="https://github.com/BurntSushi/quickcheck">quickcheck</a>.</p>
<p>More info <a href="https://docs.rs/nthash/">in the docs</a>, and if you want an <s>optimization</s> versioning bug
discussion be sure to check the <a href="https://github.com/luizirber/nthash_bug"><code>ntHash bug?</code></a> repo,
which has a (slow) Python implementation and a pretty nice <a href="https://nbviewer.jupyter.org/github/luizirber/nthash_bug/blob/master/analysis.ipynb">analysis</a> notebook.</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://social.lasanha.org/@luizirber/100721133117928424">Thread on Mastodon</a></li>
<li><a href="https://twitter.com/luizirber/status/1040386666089705472">Thread on Twitter</a></li>
</ul>
2018-09-13T20:00:00+00:00
luizirber
-
Gabbleblotchits: Oxidizing sourmash: WebAssembly
https://blog.luizirber.org/2018/08/27/sourmash-wasm/
<p>sourmash calculates MinHash signatures for genomic datasets,
meaning we are reducing the data (via subsampling) to a small
representative subset (a signature) capable of answering one question:
how similar is this dataset to another one? The key here is that a dataset with
10-100 GB will be reduced to something in the megabytes range, and two approaches
for doing that are:</p>
<ul>
<li>The user install our software in their computer.
This is not so bad anymore (yay bioconda!), but still requires knowledge
about command line interfaces and how to install all this stuff. The user
data never leaves their computer, and they can share the signatures later
if they want to.</li>
<li>Provide a web service to calculate signatures. In this case no software
need to be installed, but it's up to someone (me?) to maintain a server running with
an API and frontend to interact with the users. On top of requiring more
maintenance, another drawback is that
the user need to send me the data, which is very inefficient network-wise
and lead to questions about what I can do with their raw data (and I'm not
into surveillance capitalism, TYVM).</li>
</ul>
<h2>But... what if there is a third way?</h2>
<p>What if we could keep the frontend code from the web service (very
user-friendly) but do all the calculations client-side (and avoid the network
bottleneck)? The main hurdle
here is that our software is implemented in Python (and C++), which are not
supported in browsers. My first solution was to write the core features of
<a href="https://github.com/luizirber/sourmash-node">sourmash in JavaScript</a>, but that quickly started hitting annoying things
like JavaScript not supporting 64-bit integers. There is also the issue of
having another codebase to maintain and keep in sync with the original sourmash,
which would be a relevant burden for us. I gave a <a href="https://drive.google.com/open?id=1JvXiDaEA4J3hmEKw6sV-VHMpuHG_sxls3fLxJOht28E">lab meeting</a> about this
approach, using a <a href="https://soursigs-dnd-luizirber.hashbase.io/">drag-and-drop UI as proof of concept</a>. It did work but it
was finicky (dealing with the 64-bit integer hashes is not fun). The good thing
is that at least I had a working UI for further testing<sup id="sf-sourmash-wasm-1-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-wasm-1" class="simple-footnote" title="even if horrible, I need to get some design classes =P">1</a></sup></p>
<p>In "<a href="https://blog.luizirber.org/2018/08/23/sourmash-rust/">Oxidizing sourmash: Python and FFI</a>" I described my road to learn Rust,
but something that I omitted was that around the same time the <code>WebAssembly</code>
support in Rust started to look better and better and was a huge influence in
my decision to learn Rust. Reimplementing the sourmash C++ extension in Rust and
use the same codebase in the browser sounded very attractive,
and now that it was working I started looking into how to use the WebAssembly
target in Rust.</p>
<h2>WebAssembly?</h2>
<p>From the <a href="https://webassembly.org/">official site</a>,</p>
<blockquote>
<p>WebAssembly (abbreviated Wasm) is a binary instruction format for a stack-based virtual machine.
Wasm is designed as a portable target for compilation of high-level languages like C/C++/Rust,
enabling deployment on the web for client and server applications.</p>
</blockquote>
<p>You can write WebAssembly by hand, but the goal is to have it as lower level
target for other languages. For me the obvious benefit is being able to use
something that is not JavaScript in the browser, even though the goal is not to replace
JS completely but complement it in a big pain point: performance. This also
frees JavaScript from being the target language for other toolchains,
allowing it to grow into other important areas (like language ergonomics).</p>
<p>Rust is not the only language targeting WebAssembly: Go 1.11 includes
<a href="https://golang.org/doc/go1.11#wasm">experimental support for WebAssembly</a>, and there are even projects bringing
the <a href="https://github.com/iodide-project/pyodide">scientific Python to the web</a> using WebAssembly. </p>
<h2>But does it work?</h2>
<p>With the <a href="https://github.com/luizirber/sourmash-rust">Rust implementation in place</a> and with all tests working on sourmash, I
added the finishing touches using <a href="https://github.com/rustwasm/wasm-bindgen"><code>wasm-bindgen</code></a> and built an NPM package using
<a href="https://github.com/rustwasm/wasm-pack"><code>wasm-pack</code></a>: <a href="https://www.npmjs.com/package/sourmash">sourmash</a> is a Rust codebase compiled to WebAssembly and ready
to use in JavaScript projects.</p>
<p>(Many thanks to Madicken Munk, who also presented during SciPy about how they used
<a href="https://munkm.github.io/2018-07-13-scipy/">Rust and WebAssembly to do interactive visualization in Jupyter</a>
and helped with a good example on how to do this properly =] )</p>
<p>Since I already had the working UI from the previous PoC, I <a href="https://github.com/luizirber/wort-dnd">refactored the code</a>
to use the new WebAssembly module and voilà! <a href="https://wort-dnd.hashbase.io/">It works!</a><sup id="sf-sourmash-wasm-2-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-wasm-2" class="simple-footnote" title="the first version of this demo only worked in Chrome because they implemented the BigInt proposal, which is not in the official language yet. The funny thing is that BigInt would have made the JS implementation of sourmash viable, and I probably wouldn't have written the Rust implementation =P. Turns out that I didn't need the BigInt support if I didn't expose any 64-bit integers to JS, and that is what I'm doing now.">2</a></sup>.
<sup id="sf-sourmash-wasm-3-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-wasm-3" class="simple-footnote" title="Along the way I ended up writing a new FASTQ parser... because it wouldn't be bioinformatics if it didn't otherwise, right? =P">3</a></sup>
But that was the demo from a year ago with updated code and I got a bit
better with frontend development since then, so here is the new demo:</p>
<div id="files" class="box">
<h2>sourmash + Wasm</h2>
<div id="drag-container">
<p><b>Drag & drop</b> a FASTA or FASTQ file here to calculate the sourmash signature.</p>
</div>
<div id="progress-container">
<div id="progress-bar"></div>
</div>
<div class="columns">
<fieldset class="box input-button" id="params">
<label for="ksize-input">k-mer size:</label>
<input id="ksize-input" type="number" value="21" />
<label for="scaled-input">scaled:</label>
<input id="scaled-input" type="number" value="0" />
<label for="num-input">number of hashes:</label>
<input id="num-input" type="number" value="500" />
<label for="dna-protein-group">Input type:</label>
<div id="dna-protein-group">
<input id="dna-input" name="dna-protein-input" type="radio" value="DNA/RNA" checked="checked" />
<label for="dna-input">DNA/RNA</label>
<input id="protein-input" name="dna-protein-input" type="radio" value="Protein" />
<label for="protein-input">Protein</label>
</div>
<label for="track-abundance-input">Track abundance?</label>
<input id="track-abundance-input" type="checkbox" checked="checked" />
</fieldset>
<div class="box" id="download">
<button id="download_btn" type="button" disabled="disabled">Download</button>
</div>
</div>
</div>
<p>
</p>
<p>For the source code for this demo, check the <a href="https://blog.luizirber.org/static/sourmash-wasm/index.html">sourmash-wasm</a> directory.</p>
<h2>Next steps</h2>
<p>The proof of concept works, but it is pretty useless right now.
I'm thinking about building it as a <a href="https://www.webcomponents.org/">Web Component</a> and making it really easy
to add to any webpage<sup id="sf-sourmash-wasm-4-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-wasm-4" class="simple-footnote" title="or maybe a React component? I really would like to have something that works independent of framework, but not sure what is the best option in this case...">4</a></sup>.</p>
<p>Another interesting feature would be supporting more input formats (the GMOD
project implemented a lot of those!), but more features are probably better
after something simple but functional is released =P</p>
<h2>Next time!</h2>
<p>Where we will go next? Maybe explore some decentralized web technologies like
IPFS and dat, hmm? =]</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://social.lasanha.org/@luizirber/100624574917435477">Thread on Mastodon</a></li>
<li><a href="https://www.reddit.com/r/rust/comments/9atie8/blog_post_clientside_bioinformatics_in_the/">Thread on reddit</a></li>
<li><a href="https://twitter.com/luizirber/status/1034206952773935104">Thread on Twitter</a></li>
</ul>
<h2>Updates</h2>
<ul>
<li>2018-08-30: Added a demo in the blog post.</li>
</ul><hr /><h2>Footnotes</h2><ol><li id="sf-sourmash-wasm-1"><p>even if horrible, I
need to get some design classes =P <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-wasm-1-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-wasm-2"><p>the first version
of this demo only worked in Chrome because they implemented the <a href="https://github.com/tc39/proposal-bigint">BigInt proposal</a>,
which is not in the official language yet. The funny thing is that BigInt would
have made the JS implementation of sourmash viable, and I probably wouldn't have
written the Rust implementation =P.
Turns out that I didn't need the BigInt support if I didn't expose any 64-bit
integers to JS, and that is what I'm doing now. <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-wasm-2-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-wasm-3"><p>Along the way I ended up writing a new FASTQ parser... because it wouldn't
be bioinformatics if it didn't otherwise, right? =P <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-wasm-3-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-wasm-4"><p>or maybe a React component? I really would like to
have something that works independent of framework, but not sure what is the
best option in this case... <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-wasm-4-back" class="simple-footnote-back">↩</a></p></li></ol>
2018-08-27T18:30:00+00:00
luizirber
-
Gabbleblotchits: Oxidizing sourmash: Python and FFI
https://blog.luizirber.org/2018/08/23/sourmash-rust/
<p>I think the first time I heard about Rust was because Frank Mcsherry chose it to
write a <a href="https://github.com/frankmcsherry/timely-dataflow">timely dataflow</a> implementation.
Since then it started showing more and more in my news sources,
leading to Armin Ronacher publishing a post in the Sentry blog last November about
writing <a href="https://blog.sentry.io/2017/11/14/evolving-our-rust-with-milksnake">Python extensions in Rust</a>.</p>
<p>Last December I decided to give it a run:
I spent some time porting the C++ bits of <a href="https://github.com/dib-lab/sourmash">sourmash</a>
to Rust.
The main advantage here is that it's a problem I know well,
so I know what the code is supposed to do and can focus on figuring out
syntax and the mental model for the language.
I started digging into the <code>symbolic</code> codebase and understanding what they did,
and tried to mirror or improve it for my use cases.</p>
<p>(About the post title: The process of converting a codebase to Rust is referred as <a href="https://wiki.mozilla.org/Oxidation">"Oxidation"</a> in
the Rust community, following the codename Mozilla chose for the process of
integrating Rust components into the Firefox codebase.
<sup id="sf-sourmash-rust-1-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-rust-1" class="simple-footnote" title="The creator of the language is known to keep making up different explanations for the name of the language, but in this case ">1</a></sup>
Many of these components were tested and derived in Servo, an experimental
browser engine written in Rust, and are being integrated into Gecko,
the current browser engine (mostly written in C++).)</p>
<h2>Why Rust?</h2>
<p>There are other programming languages more focused on scientific software
that could be used instead, like Julia<sup id="sf-sourmash-rust-2-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-rust-2" class="simple-footnote" title="Even more now that it hit 1.0, it is a really nice language">2</a></sup>. Many programming languages start from a
specific niche (like R and statistics,
or Maple and mathematics) and grow into larger languages over time.
While Rust goal is not to be a scientific language,
its focus on being a general purpose language allows a phenomenon similar
to what happened with Python, where people from many areas pushed the
language in different directions (system scripting, web development,
numerical programming...) allowing developers to combine all these things
in their systems.</p>
<p>But by far my interest in Rust is for the many best practices it brings to the default experience:
integrated package management (with Cargo),
documentation (with rustdoc), testing and benchmarking.
It's understandable that older languages like C/C++ need
more effort to support some of these features (like modules and an unified
build system), since they are designed by standard and need to keep backward
compatibility with codebases that already exist.
Nonetheless, the lack of features increase the effort needed to have good
software engineering practices, since you need to choose a solution that might
not be compatible with other similar but slightly different options,
leading to fragmentation and increasing the impedance to use these features.</p>
<p>Another big reason is that Rust doesn't aim to completely replace what already
exists, but complement and extend it. Two very good talks about how to do this,
one by <a href="https://ashleygwilliams.github.io/rustfest-2017/#1">Ashley Williams</a>, another by <a href="https://talks.edunham.net/lca2018/should-you-rewrite-in-rust/beamer.pdf">E. Dunham</a>.</p>
<h2>Converting from a C++ extension to Rust</h2>
<p>The current implementation of the core data structures in sourmash is in a
C++ extension wrapped with Cython. My main goals for converting the code are:</p>
<ul>
<li>
<p>support additional languages and platforms. sourmash is available as a Python
package and CLI, but we have R users in the lab that would benefit from having
an R package, and ideally we wouldn't need to rewrite the software every time
we want to support a new language.</p>
</li>
<li>
<p>reducing the number of wheel packages necessary (one for each OS/platform).</p>
</li>
<li>
<p>in the long run, use the Rust memory management concepts (lifetimes, borrowing)
to increase parallelism in the code.</p>
</li>
</ul>
<p>Many of these goals are attainable with our current C++ codebase, and
"rewrite in a new language" is rarely the best way to solve a problem.
But the reduced burden in maintenance due to better tooling,
on top of features that would require careful planning to execute
(increasing the parallelism without data races) while maintaining compatibility
with the current codebase are promising enough to justify this experiment.
<a href="https://github.com/luizirber/2018-python-rust/blob/master/03.current_impl.md"><img src="https://blog.luizirber.org/images/arch_cpp.png" title="" /></a></p>
<p>Cython provides a nice gradual path to migrate code from Python to C++,
since it is a superset of the Python syntax. It also provides low overhead
for many C++ features, especially the STL containers, with makes it easier
to map C++ features to the Python equivalent.
For research software this also lead to faster exploration of solutions before
having to commit to lower level code, but without a good process it might also
lead to code never crossing into the C++ layer and being stuck in the Cython
layer. This doesn't make any difference for a Python user, but it becomes
harder from users from other languages to benefit from this code (since your
language would need some kind of support to calling Python code, which is not
as readily available as calling C code).</p>
<p>Depending on the requirements, a downside is that Cython is tied to the CPython API,
so generating the extension requires a development environment set up with
the appropriate headers and compiler. This also makes the extension specific
to a Python version: while this is not a problem for source distributions,
generating wheels lead to one wheel for each OS and Python version supported.</p>
<h2>The new implementation</h2>
<p>This is the overall architecture of the Rust implementation:
<a href="https://github.com/luizirber/2018-python-rust/blob/master/04.rust_impl.md"><img src="https://blog.luizirber.org/images/arch_rust.png" title="" /></a>
It is pretty close to what <code>symbolic</code> does,
so let's walk through it.</p>
<h3>The Rust code</h3>
<p>If you take a look at my Rust code, you will see it is very... C++. A lot of the
code is very similar to the original implementation, which is both a curse and a
blessing: I'm pretty sure that are more idiomatic and performant ways of doing
things, but most of the time I could lean on my current mental model for C++ to
translate code. The biggest exception was the <code>merge</code> function, were I was doing
something on the C++ implementation that the borrow checker didn't like.
Eventually I found it was because it couldn't keep track of the lifetime
correctly and putting braces around it fixed the problem,
which was both an epiphany and a WTF moment. <a href="https://play.rust-lang.org/?gist=eae9de12950d1b2a7699cd49a3571c37&version=stable">Here</a> is an example that triggers
the problem, and the <a href="https://play.rust-lang.org/?gist=c8733c5125766930a589c8d0412af99c&version=stable">solution</a>.</p>
<p>"Fighting the borrow checker" seems to be a common theme while learning Rust,
but the compiler really tries to help you to understand what is happening and
(most times) how to fix it. A lot of people grow to hate the borrow checker,
but I see it more as a 'eat your vegetables' situation: you might not like it
at first, but it's better in the long run. Even though I don't have a big
codebase in Rust yet, it keeps you from doing things that will come back to bite
you hard later.</p>
<h3>Generating C headers for Rust code: cbindgen</h3>
<p>With the Rust library working, the next step was taking the Rust code and generate C headers describing the
functions and structs we expose with the <code>#[no_mangle]</code> attribute in Rust
(these are defined in the <a href="https://github.com/luizirber/sourmash-rust/blob/ead9ae0ed3b2d16c9e3b8379919f3bfd2efd21ae/src/ffi.rs"><code>ffi.rs</code></a> module in <code>sourmash-rust</code>).
This attribute tells the Rust compiler to generate names that are compatible
with the C ABI, and so can be called from other languages that implement FFI
mechanisms. FFI (the foreign function interface) is quite low-level,
and pretty much defines things that C can represent: integers, floats, pointers
and structs. It doesn't support higher level concepts like objects or generics,
so in a sense it looks like a feature funnel.
This might sound bad, but ends up being something that other languages can
understand without needing too much extra functionality in their runtimes,
which means that most languages have support to calling code through an FFI.</p>
<p>Writing the C header by hand is possible, but is very error prone.
A better solution is to use <a href="https://github.com/eqrion/cbindgen"><code>cbindgen</code></a>,
a program that takes Rust code and generate a C header file automatically.
<code>cbindgen</code> is developed primarily to generate the C headers for <a href="https://github.com/servo/webrender/">webrender</a>,
the GPU-based renderer for servo,
so it's pretty likely that if it can handle a complex codebase it will work
just fine for the majority of projects.</p>
<h3>Interfacing with Python: CFFI and Milksnake</h3>
<p>Once we have the C headers, we can use the FFI to
call Rust code in Python. Python has a FFI module in the standard library: <code>ctypes</code>,
but the Pypy developers also created CFFI, which has more features.</p>
<p>The C headers generated by cbindgen can be interpreted by CFFI to generate
a low-level Python interface for the code. This is the equivalent of declaring
the functions/methods and structs/classes in a <code>pxd</code> file (in the Cython
world): while the code is now usable in Python, it is not well adapted to
the features and idioms available in the language.</p>
<p>Milksnake is the package developed by Sentry that takes care of running cargo
for the Rust compilation and generating the CFFI boilerplate,
making it easy to load the low-level CFFI bindings in Python.
With this low-level binding available we can now write something more Pythonic
(the <code>pyx</code> file in Cython), and I ended up just renaming the <code>_minhash.pyx</code> file
back to <code>minhash.py</code> and doing one-line fixes to replace the Cython-style code
with the equivalent CFFI calls.</p>
<p>All of these changes should be transparent to the Python code, and to guarantee
that I made sure that all the current tests that we have (both for the Python
module and the command line interface) are still working after the changes.
It also led to finding some quirks in the implementation,
and even improvements in the current C++ code (because we were moving a lot of
data from C++ to Python).</p>
<h2>Where I see this going</h2>
<p>It seems it worked as an experiment,
and I presented a <a href="https://github.com/luizirber/2018-python-rust">poster</a> at <a href="https://gccbosc2018.sched.com/event/FEWp/b23-oxidizing-python-writing-extensions-in-rust">GCCBOSC 2018</a> and <a href="https://scipy2018.scipy.org/ehome/index.php?eventid=299527&tabid=712461&cid=2233543&sessionid=21618890&sessionchoice=1&">SciPy 2018</a> that
was met with excitement by many people.
Knowing that it is possible,
I want to reiterate some points why Rust is pretty exciting for bioinformatics
and science in general.</p>
<h3>Bioinformatics as libraries (and command line tools too!)</h3>
<p>Bioinformatics is an umbrella term for many different methods, depending on
what analysis you want to do with your data (or model).
In this sense, it's distinct from other scientific areas where it is possible
to rely on a common set of libraries (numpy in linear algebra, for example), since a
library supporting many disjoint methods tend to grow too big and hard to
maintain.</p>
<p>The environment also tends to be very diverse, with different languages being
used to implement the software. Because it is hard to interoperate,
the methods tend to be implemented in command line programs that are stitched
together in pipelines, a workflow describing how to connect the input and output of many different tools to
generate results.
Because the basic unit is a command-line tool,
pipelines tend to rely on standard operating system abstractions like
files and pipes to make the tools communicate with each other. But since tools
might have input requirements distinct from what the previous tool provides,
many times it is necessary to do format conversion or other adaptations to make the
pipeline work.</p>
<p>Using tools as blackboxes, controllable through specific parameters at the
command-line level, make exploratory analysis and algorithm reuse harder:
if something needs to be investigated the user needs to resort to perturbations
of the parameters or the input data, without access to the more feature-rich and
meaningful abstraction happening inside the tool.</p>
<p>Even if many languages are used for writing the software, most of the time there
is some part written in C or C++ for performance reasons, and these tend to be
the core data structures of the computational method. Because it is not easy to
package your C/C++ code in a way that other people can readily use it,
most of this code is reinvented over and over again, or is copy and pasted into
codebases and start diverging over time. Rust helps solve this problem with the
integrated package management, and due to the FFI it can also be reused inside
other programs written in other languages.</p>
<p>sourmash is not going to be Rust-only and abandon Python,
and it would be crazy to do so when it has so many great exploratory tools
for scientific discovery. But now we can also use our method in other languages
and environment, instead of having our code stuck in one language.</p>
<h3>Don't rewrite it all!</h3>
<p>I could have gone all the way and rewrite sourmash in Rust<sup id="sf-sourmash-rust-3-back"><a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-rust-3" class="simple-footnote" title="and risk being kicked out of the lab =P">3</a></sup>, but it would be incredibly disruptive for
the current sourmash users and it would take way longer to pull off. Because
Rust is so focused in supporting existing code, you can do a slow transition and
reuse what you already have while moving into more and more Rust code.
A great example is this one-day effort by Rob Patro to bring <a href="https://github.com/COMBINE-lab/cqf-rust">CQF</a> (a C
codebase) into Rust, using <code>bindgen</code> (a generator of C bindings for Rust).
Check <a href="https://blog.luizirber.org/2018/08/27/sourmash-wasm/">the Twitter thread</a> for more =]</p>
<h3>Good scientific citizens</h3>
<p>There is another MinHash implementation already written in Rust, <a href="https://github.com/onecodex/finch-rs">finch</a>.
Early in my experiment I got an email from them asking if I wanted to work
together, but since I wanted to learn the language I kept doing my thing. (They
were totally cool with this, by the way). But the fun thing is that Rust has a
pair of traits called <a href="https://doc.rust-lang.org/rust-by-example/conversion/from_into.html"><code>From</code> and <code>Into</code></a> that you can implement for your
type, and so I <a href="https://github.com/luizirber/sourmash-rust/pull/1">did that</a> and now we can have interoperable
implementations. This synergy allows <code>finch</code> to use <code>sourmash</code> methods,
and vice versa.</p>
<p>Maybe this sounds like a small thing, but I think it is really exciting. We can
stop having incompatible but very similar methods, and instead all benefit from
each other advances in a way that is supported by the language.</p>
<h2>Next time!</h2>
<p>Turns out Rust supports WebAssembly as a target,
so... what if we run sourmash in the browser?
That's what I'm covering in the <a href="https://blog.luizirber.org/2018/08/27/sourmash-wasm/">next blog post</a>,
so stay tuned =]</p>
<h2>Comments?</h2>
<ul>
<li><a href="https://social.lasanha.org/@luizirber/100602525575698239">Thread on Mastodon</a></li>
<li><a href="https://www.reddit.com/r/rust/comments/99vakd/blog_post_converting_c_to_rust_and_interoperate/">Thread on reddit</a></li>
<li><a href="https://twitter.com/luizirber/status/1032779995129597952">Thread on Twitter</a></li>
</ul>
<!-- rust libs -->
<!-- why rust -->
<!-- WASM and demos -->
<!-- python and FFI -->
<!-- rust examples -->
<!-- cbindgen -->
<!-- rust for pythonistas -->
<!-- understanding rust -->
<!-- rust and bio -->
<!-- rust error handling -->
<!-- other notes
- Implement Default trait for defaults (less verbose than ::new...)
https://doc.rust-lang.org/std/default/trait.Default.html
--><hr /><h2>Footnotes</h2><ol><li id="sf-sourmash-rust-1"><p>The creator of the language is known to keep making up different
explanations for the <a href="https://www.reddit.com/r/rust/comments/27jvdt/internet_archaeology_the_definitive_endall_source/">name of the language</a>,
but in this case "oxidation" refers to the chemical process that creates
rust, and rust is the closest thing to metal (metal being the hardware).
There are many terrible puns in the Rust community. <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-rust-1-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-rust-2"><p>Even more now that it hit 1.0,
it is a really nice language <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-rust-2-back" class="simple-footnote-back">↩</a></p></li><li id="sf-sourmash-rust-3"><p>and risk
being kicked out of the lab =P <a href="https://blog.luizirber.org/atom.xml?tag=python#sf-sourmash-rust-3-back" class="simple-footnote-back">↩</a></p></li></ol>
2018-08-23T20:00:00+00:00
luizirber
-
PythonClub: Upload de Arquivos com Socket e Struct
https://pythonclub.com.br/upload-de-arquivos-com-socket-e-struct.html
<p>Apesar de termos muitas formas de enviarmos arquivos para servidores hoje em dia, como por exemplo o <em>scp</em> e <em>rsync</em>, podemos usar o python com seus módulos <em>built-in</em> para enviar arquivos a servidores usando struct para serializar os dados e socket para criar uma conexão cliente/servidor.</p>
<h3><em>Struct</em></h3>
<p>O módulo <a href="https://docs.python.org/3/library/struct.html">struct</a> é usado para converter bytes no python em formatos do struct em C.
Com ele podemos enviar num único conjunto de dados o nome de um arquivo e os bytes referentes ao seus dados.</p>
<p>Struct também é utilizado para serializar diversos tipos de dados diferentes, como bytes, inteiros, floats além de outros, no nosso caso usaremos apenas bytes.</p>
<p>Vamos criar um arquivo para serializar.</p>
<div class="highlight"><pre><span></span><span class="err">!</span><span class="n">echo</span> <span class="s2">"Upload de arquivos com sockets e struct</span><span class="se">\n</span><span class="s2">Criando um arquivo para serializar."</span> <span class="o">></span> <span class="n">arquivo_para_upload</span>
</pre></div>
<p>Agora em posse de um arquivo vamos criar nossa estrutura de bytes para enviar.</p>
<div class="highlight"><pre><span></span><span class="n">arquivo</span> <span class="o">=</span> <span class="s2">"arquivo_para_upload"</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="n">arquivo</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">arq</span><span class="p">:</span>
<span class="n">dados_arquivo</span> <span class="o">=</span> <span class="n">arq</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">serializar</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">Struct</span><span class="p">(</span><span class="s2">"</span><span class="si">{}</span><span class="s2">s </span><span class="si">{}</span><span class="s2">s"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">arquivo</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">dados_arquivo</span><span class="p">)))</span>
<span class="n">dados_upload</span> <span class="o">=</span> <span class="n">serializar</span><span class="o">.</span><span class="n">pack</span><span class="p">(</span><span class="o">*</span><span class="p">[</span><span class="n">arquivo</span><span class="o">.</span><span class="n">encode</span><span class="p">(),</span> <span class="n">dados_arquivo</span><span class="p">])</span>
</pre></div>
<p>Por padrão, struct usa caracteres no início da sequência dos dados para definir a ordem dos bytes, tamanho e alinhamento dos bytes nos dados empacotados.
Esses caracteres podem ser vistos na <a href="https://docs.python.org/3/library/struct.html#byte-order-size-and-alignment">seção 7.1.2.1</a> da documentação.
Como não definimos, será usado o <strong>@</strong> que é o padrão.</p>
<p>Nessa linha:</p>
<div class="highlight"><pre><span></span><span class="n">serializar</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">Struct</span><span class="p">(</span><span class="s2">"</span><span class="si">{}</span><span class="s2">s </span><span class="si">{}</span><span class="s2">s"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">arquivo</span><span class="p">),</span> <span class="nb">len</span><span class="p">(</span><span class="n">dados_arquivo</span><span class="p">)))</span>
</pre></div>
<p>definimos que nossa estrutura serializada seria de dois conjuntos de caracteres, a primeira com o tamanho do nome do arquivo, e a segunda com o tamanho total dos dados lidos em</p>
<div class="highlight"><pre><span></span><span class="n">dados_arquivo</span> <span class="o">=</span> <span class="n">arq</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
</pre></div>
<p>Se desempacotarmos os dados, teremos uma lista com o nome do arquivo e os dados lidos anteriormente.</p>
<div class="highlight"><pre><span></span><span class="n">serializar</span><span class="o">.</span><span class="n">unpack</span><span class="p">(</span><span class="n">dados_upload</span><span class="p">)[</span><span class="mi">0</span><span class="p">]</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="err">b'arquivo_para_upload'</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="n">serializar</span><span class="o">.</span><span class="n">unpack</span><span class="p">(</span><span class="n">dados_upload</span><span class="p">)[</span><span class="mi">1</span><span class="p">]</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="err">b'Upload de arquivos com sockets e struct\\nCriando um arquivo para serializar.\n'</span>
</pre></div>
<p>Agora de posse dos nossos dados já serializados, vamos criar um cliente e um servidor com socket para transferirmos nosso arquivo.</p>
<h3><em>Socket</em></h3>
<p>O modulo <a href="https://docs.python.org/3/library/socket.html">socket</a> prove interfaces de socket BSD, disponiveis em praticamente todos os sistemas operacionais.</p>
<h4>Familias de sockets</h4>
<p>Diversas famílias de sockets podem ser usadas para termos acessos a objetos que nos permitam fazer chamadas de sistema.
Mais informações sobre as famílias podem ser encontradas na <a href="https://docs.python.org/3/library/socket.html#socket-families">seção 18.1.1</a> da documentação. No nosso exemplo usaremos a AF_INET.</p>
<h4>AF_INET</h4>
<p><strong>AF_INET</strong> precisa basicamente de um par de dados, contendo um endereço IPv4 e uma porta para ser instanciada.
Para endereços IPv6 o modulo disponibiliza o <strong>AF_INET6</strong></p>
<h4>Constante [SOCK_STREAM]</h4>
<p>As constantes representam as famílias de sockets, como a constante AF_INET e os protocolos usados como parâmetros para o modulo socket.
Um dos protocolos mais usados encontrados na maioria dos sistemas é o SOCK_STREAM.</p>
<p>Ele é um protocolo baseado em comunicação que permite que duas partes estabeleçam uma conexão e conversem entre si.</p>
<h3><em>Servidor e cliente socket</em></h3>
<p>Como vamos usar um protocolo baseado em comunicação, iremos construir o servidor e cliente paralelamente para um melhor entendimento.</p>
<blockquote>
<p>Servidor</p>
</blockquote>
<p>Para esse exemplo eu vou usar a porta 6124 para o servidor, ela esta fora da range reservada pela IANA para sistemas conhecidos, que vai de 0-1023.</p>
<p>Vamos importar a bilioteca socket e definir um host e porta para passarmos como parametro para a constante AF_INET.</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">socket</span>
<span class="n">host</span> <span class="o">=</span> <span class="s2">"127.0.0.1"</span>
<span class="n">porta</span> <span class="o">=</span> <span class="mi">6124</span>
<span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span>
</pre></div>
<p>Agora usaremos o metodo bind para criarmos um ponto de conexão para nosso cliente. Esse método espera por uma tupla contento o host e porta como parâmetros.</p>
<div class="highlight"><pre><span></span><span class="n">sock</span><span class="o">.</span><span class="n">bind</span><span class="p">((</span><span class="n">host</span><span class="p">,</span> <span class="n">porta</span><span class="p">))</span>
</pre></div>
<p>Agora vamos colocar nosso servidor socket em modo escuta com o metodo listen. Esse método recebe como parâmetro um número inteiro (<strong>backlog</strong>) definindo qual o tamanho da fila que será usada para receber pacotes SYN até dropar a conexão. Usaremos um valor baixo o que evita SYN flood na rede. Mais informações sobre <em>backlog</em> podem ser encontradas na <a href="https://tools.ietf.org/html/rfc7413">RFC 7413</a>.</p>
<div class="highlight"><pre><span></span><span class="n">sock</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span>
</pre></div>
<p>Agora vamos colocar o nosso socket em um loop esperando por uma conexão e um início de conversa. Pra isso vamos usar o metodo <em>accept</em> que nos devolve uma tupla, onde o primeiro elemento é um novo objeto socket para enviarmos e recebermos informações, e o segundo contendo informações sobre o endereço de origem e porta usada pelo cliente.</p>
<p><strong>Vamos criar um diretório para salvar nosso novo arquivo.</strong></p>
<div class="highlight"><pre><span></span><span class="err">!</span><span class="n">mkdir</span> <span class="n">arquivos_recebidos</span>
</pre></div>
<p><em>Os dados são enviados sempre em bytes</em>. <strong>Leia os comentários</strong></p>
<div class="highlight"><pre><span></span><span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
<span class="n">novo_sock</span><span class="p">,</span> <span class="n">cliente</span> <span class="o">=</span> <span class="n">sock</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
<span class="k">with</span> <span class="n">novo_sock</span><span class="p">:</span> <span class="c1"># Caso haja uma nova conexão</span>
<span class="n">ouvir</span> <span class="o">=</span> <span class="n">novo_sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> <span class="c1"># Colocamos nosso novo objeto socket para ouvir</span>
<span class="k">if</span> <span class="n">ouvir</span> <span class="o">!=</span> <span class="sa">b</span><span class="s2">""</span><span class="p">:</span> <span class="c1"># Se houver uma mensagem...</span>
<span class="sd">"""</span>
<span class="sd"> Aqui usaremos os dados enviados na mensagem para criar nosso serielizador.</span>
<span class="sd"> Com ele criado poderemos desempacotar os dados assim que recebermos.</span>
<span class="sd"> Veja no cliente mais abaixo qual a primeira mensagem enviada.</span>
<span class="sd"> """</span>
<span class="n">mensagem</span><span class="p">,</span> <span class="n">nome</span><span class="p">,</span> <span class="n">dados</span> <span class="o">=</span> <span class="n">ouvir</span><span class="o">.</span><span class="n">decode</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="n">serializar</span> <span class="o">=</span> <span class="n">struct</span><span class="o">.</span><span class="n">Struct</span><span class="p">(</span><span class="s2">"</span><span class="si">{}</span><span class="s2">s </span><span class="si">{}</span><span class="s2">s"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="n">nome</span><span class="o">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">0</span><span class="p">]),</span> <span class="nb">int</span><span class="p">(</span><span class="n">dados</span><span class="o">.</span><span class="n">split</span><span class="p">()[</span><span class="mi">0</span><span class="p">])))</span>
<span class="n">novo_sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s2">"Pode enviar!"</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span> <span class="c1"># Enviaremos uma mensagem para o cliente enviar os dados.</span>
<span class="n">dados</span> <span class="o">=</span> <span class="n">novo_sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> <span class="c1"># Agora iremos esperar por eles.</span>
<span class="n">nome</span><span class="p">,</span> <span class="n">arquivo</span> <span class="o">=</span> <span class="n">serializar</span><span class="o">.</span><span class="n">unpack</span><span class="p">(</span><span class="n">dados</span><span class="p">)</span> <span class="c1"># Vamos desempacotar os dados</span>
<span class="sd">"""</span>
<span class="sd"> Agora enviamos uma mensagem dizendo que o arquivo foi recebido.</span>
<span class="sd"> E iremos salva-lo no novo diretório criado.</span>
<span class="sd"> """</span>
<span class="n">novo_sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s2">"Os dados do arquivo </span><span class="si">{}</span><span class="s2"> foram enviados."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">nome</span><span class="o">.</span><span class="n">decode</span><span class="p">())</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s2">"arquivos_recebidos/</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">nome</span><span class="o">.</span><span class="n">decode</span><span class="p">()),</span> <span class="s1">'wb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">novo_arquivo</span><span class="p">:</span>
<span class="n">novo_arquivo</span><span class="o">.</span><span class="n">write</span><span class="p">(</span><span class="n">arquivo</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Arquivo </span><span class="si">{}</span><span class="s2"> salvo em arquivos_recebidos."</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">nome</span><span class="o">.</span><span class="n">decode</span><span class="p">()))</span>
<span class="n">Arquivo</span> <span class="n">arquivo_para_upload</span> <span class="n">salvo</span> <span class="n">em</span> <span class="n">arquivos_recebidos</span><span class="o">.</span>
</pre></div>
<blockquote>
<p>Cliente</p>
</blockquote>
<p>Nosso cliente irá usar o metodo <em>connect</em> para se conectar no servidor e a partir dai começar enviar e receber mensagens. Ele também recebe como parâmetros uma tupla com o host e porta de conexão do servidor.</p>
<div class="highlight"><pre><span></span><span class="n">host</span> <span class="o">=</span> <span class="s1">'127.0.0.1'</span>
<span class="n">porta</span> <span class="o">=</span> <span class="mi">6124</span>
<span class="n">sock</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">AF_INET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">)</span> <span class="c1"># Cria nosso objeto socket</span>
<span class="n">sock</span><span class="o">.</span><span class="n">connect</span><span class="p">((</span><span class="n">host</span><span class="p">,</span> <span class="n">porta</span><span class="p">))</span>
<span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="s2">"Enviarei um arquivo chamado: </span><span class="si">{}</span><span class="s2"> contendo: </span><span class="si">{}</span><span class="s2"> bytes"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span>
<span class="n">arquivo</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">dados_arquivo</span><span class="p">))</span><span class="o">.</span><span class="n">encode</span><span class="p">())</span> <span class="c1"># Enviamos a mensagem com o nome e tamanho do arquivo.</span>
<span class="n">ouvir</span> <span class="o">=</span> <span class="n">sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> <span class="c1"># Aguardamos uma mensagem de confirmação do servidor.</span>
<span class="k">if</span> <span class="n">ouvir</span><span class="o">.</span><span class="n">decode</span><span class="p">()</span> <span class="o">==</span> <span class="s2">"Pode enviar!"</span><span class="p">:</span>
<span class="n">sock</span><span class="o">.</span><span class="n">send</span><span class="p">(</span><span class="n">dados_upload</span><span class="p">)</span> <span class="c1"># Enviamos os dados empacotados.</span>
<span class="n">resposta</span> <span class="o">=</span> <span class="n">sock</span><span class="o">.</span><span class="n">recv</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span> <span class="c1"># Aguardamos a confirmação de que os dados foram enviados.</span>
<span class="nb">print</span><span class="p">(</span><span class="n">resposta</span><span class="o">.</span><span class="n">decode</span><span class="p">())</span>
<span class="n">Os</span> <span class="n">dados</span> <span class="n">do</span> <span class="n">arquivo</span> <span class="n">arquivo_para_upload</span> <span class="n">foram</span> <span class="n">enviados</span><span class="o">.</span>
</pre></div>
<p>Agora podemos checar nossos arquivos e ver se eles foram salvos corretamente.</p>
<div class="highlight"><pre><span></span><span class="err">!</span><span class="n">md5sum</span> <span class="n">arquivo_para_upload</span><span class="p">;</span> <span class="n">md5sum</span> <span class="n">arquivos_recebidos</span><span class="o">/</span><span class="n">arquivos_para_upload</span>
<span class="mf">605e99</span><span class="n">b3d873df0b91d8834ff292d320</span> <span class="n">arquivo_para_upload</span>
<span class="mf">605e99</span><span class="n">b3d873df0b91d8834ff292d320</span> <span class="n">arquivos_recebidos</span><span class="o">/</span><span class="n">arquivo_para_upload</span>
</pre></div>
<p>Com base nesse exemplo, podem ser enviados diversos arquivos, sendo eles texto, arquivos compactados ou binários.</p>
<p>Sem mais delongas, fiquem com Cher e até a próxima!</p>
<p>Abraços.</p>
2018-05-17T22:24:00+00:00
Silvio Ap Silva
-
PythonClub: Monitorando Ips Duplicados na Rede
https://pythonclub.com.br/monitorando-ips-duplicados-na-rede.html
<p>Muitos administradores de redes e sysadmins encontram problemas de conectividade nos ambientes que administram e por muitas vezes o problema é um simples IP duplicado causando todo mal estar. Agora veremos como usar o scapy e defaultdict da lib collections para monitorar esses IPs.</p>
<h3>Scapy</h3>
<p>O Scapy é uma poderosa biblioteca de manipulação de pacotes interativa, com abrangencia a uma enorme quantidade de protocolos provenientes da suite TCP/IP.
Mais informações sobre o scpay pode ser encontrada na <a href="https://scapy.readthedocs.io/en/latest/index.html"><strong>documentação oficial</strong></a>.
Nesse caso em especifico iremos utilizar do scapy a metaclasse <em>ARP</em> e a função <em>sniff</em>.</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">scapy.all</span> <span class="kn">import</span> <span class="n">ARP</span><span class="p">,</span> <span class="n">sniff</span>
</pre></div>
<h4>sniff</h4>
<p>Vamos usar a função sniff para monitorar os pacotes que trafegam na rede usando o protocolo ARP.
Pra isso vamos utilizar dela quatro parametros basicos:</p>
<div class="highlight"><pre><span></span><span class="n">sniff</span><span class="p">(</span><span class="n">prn</span><span class="o">=</span><span class="n">pacotes</span><span class="p">,</span> <span class="nb">filter</span><span class="o">=</span><span class="s2">"arp"</span><span class="p">,</span> <span class="n">iface</span><span class="o">=</span><span class="n">interface</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</pre></div>
<ul>
<li>
<p>prn, chama uma função para ser aplicada a cada pacote capturado pelo sniff.</p>
</li>
<li>
<p>filter, irá filtrar todos os pacotes que contiverem o protocolo ARP.</p>
</li>
<li>
<p>iface, determina a interface de rede que será monitorada.</p>
</li>
<li>
<p>timeout, irá determinar que nosso monitoramento da rede se dara por 60 segundos.</p>
</li>
</ul>
<h4>ARP</h4>
<p>ARP é uma metaclasse de pacotes com dados sobre o protocolo arp pertencente a camada de enlace de dados.
Iremos utilizar essa metaclasse para filtrar os pacotes com informações de pacotes com respostas a requisições arp. (opcode == 2 [is at])
As informações sobre o protocolo ARP podem serm encontradas na <a href="https://tools.ietf.org/html/rfc826">rfc826</a> no site do IETF.</p>
<h3>collections.defaultdict</h3>
<p>defaultdict é uma subclasse de dict que prove uma instancia de variavel para a chamada de uma chave inexistente.</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">collections</span> <span class="kn">import</span> <span class="n">defaultdict</span>
<span class="n">list_ips</span> <span class="o">=</span> <span class="n">defaultdict</span><span class="p">(</span><span class="nb">set</span><span class="p">)</span>
</pre></div>
<p>Basicamente nossa função irá monitorar por um certo tempo o trafego de pacotes pela rede adicionar a nossa variavel <em>list_ips</em> o endereço ou endereços MAC encontrados.</p>
<h3>Definindo a função que será passada como parametro para o sniff.</h3>
<p>Para cada pacote capturado pela função sniff, será checado se o opcode corresponde a um response do protocolo arp.
Caso seja, sera adicionado a nossa defaultdict.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">pacotes</span><span class="p">(</span><span class="n">pacote</span><span class="p">):</span>
<span class="sd">"""Checa se o valor do opcode dentro do protocolo arp é igual a 2."""</span>
<span class="k">if</span> <span class="n">pacote</span><span class="p">[</span><span class="n">ARP</span><span class="p">]</span><span class="o">.</span><span class="n">op</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
<span class="c1"># Se for adiciona o ip de origem e seu mac à dict list_ips</span>
<span class="n">list_ips</span><span class="p">[</span><span class="n">pacote</span><span class="p">[</span><span class="n">ARP</span><span class="p">]</span><span class="o">.</span><span class="n">psrc</span><span class="p">]</span><span class="o">.</span><span class="n">add</span><span class="p">(</span><span class="n">pacote</span><span class="p">[</span><span class="n">ARP</span><span class="p">]</span><span class="o">.</span><span class="n">hwsrc</span><span class="p">)</span>
</pre></div>
<h3>Limpando a tabela arp</h3>
<p>Para que seja feita novas requisições arp, iremos limpar nossa tabela arp e iniciar o monitoramento da rede.
Pra isso iremos usar o comando arp, dentro do shell do sistema. <em>(Como uso <a href="https://www.freebsd.org">FreeBSD</a> vou definir uma função chamando um comando pelo csh)</em></p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">os</span>
<span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span><span class="s1">'which arp'</span><span class="p">)</span>
<span class="o">/</span><span class="n">usr</span><span class="o">/</span><span class="n">sbin</span><span class="o">/</span><span class="n">arp</span>
</pre></div>
<p>Com posse do caminho do comando arp, irei definir uma função que limpe a tabela e inicie o monitore a rede por 60 segundos.</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">monitorar</span><span class="p">(</span><span class="n">interface</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> O comando arp no FreeBSD usa os parametros:</span>
<span class="sd"> -d para deletar as entradas</span>
<span class="sd"> -i para declarar a interface</span>
<span class="sd"> -a para representar todas entradas a serem deletas.</span>
<span class="sd"> """</span>
<span class="n">cmd</span> <span class="o">=</span> <span class="s2">"/usr/sbin/arp -d -i </span><span class="si">{}</span><span class="s2"> -a"</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">interface</span><span class="p">)</span>
<span class="n">os</span><span class="o">.</span><span class="n">system</span><span class="p">(</span><span class="n">cmd</span><span class="p">)</span>
<span class="n">sniff</span><span class="p">(</span><span class="n">prn</span><span class="o">=</span><span class="n">pacotes</span><span class="p">,</span> <span class="nb">filter</span><span class="o">=</span><span class="s2">"arp"</span><span class="p">,</span> <span class="n">iface</span><span class="o">=</span><span class="n">interface</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">)</span>
</pre></div>
<p>E por ultimo chamar a função de monitoramento.
No meu caso eu vou monitorar a interface <strong>em0</strong>.</p>
<div class="highlight"><pre><span></span><span class="n">monitorar</span><span class="p">(</span><span class="s2">"em0"</span><span class="p">)</span>
</pre></div>
<p>Agora só conferir as entradas em nossa dict.</p>
<div class="highlight"><pre><span></span><span class="k">for</span> <span class="n">ip</span> <span class="ow">in</span> <span class="n">list_ips</span><span class="p">:</span>
<span class="nb">print</span> <span class="s2">"IP: </span><span class="si">{}</span><span class="s2"> -> MACs: </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">ip</span><span class="p">,</span> <span class="s2">", "</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="nb">list</span><span class="p">(</span><span class="n">list_ips</span><span class="p">[</span><span class="n">ip</span><span class="p">])))</span>
<span class="n">IP</span><span class="p">:</span> <span class="mf">192.168</span><span class="o">.</span><span class="mf">213.1</span> <span class="o">-></span> <span class="n">MACs</span><span class="p">:</span> <span class="mi">00</span><span class="p">:</span><span class="mi">90</span><span class="p">:</span><span class="mi">0</span><span class="n">b</span><span class="p">:</span><span class="mi">49</span><span class="p">:</span><span class="mi">3</span><span class="n">d</span><span class="p">:</span><span class="mi">0</span><span class="n">a</span>
<span class="n">IP</span><span class="p">:</span> <span class="mf">192.168</span><span class="o">.</span><span class="mf">213.10</span> <span class="o">-></span> <span class="n">MACs</span><span class="p">:</span> <span class="mi">08</span><span class="p">:</span><span class="mi">00</span><span class="p">:</span><span class="mi">27</span><span class="p">:</span><span class="n">bf</span><span class="p">:</span><span class="mi">52</span><span class="p">:</span><span class="mi">6</span><span class="n">d</span><span class="p">,</span> <span class="n">a0</span><span class="p">:</span><span class="n">f3</span><span class="p">:</span><span class="n">c1</span><span class="p">:</span><span class="mi">03</span><span class="p">:</span><span class="mi">74</span><span class="p">:</span><span class="mi">6</span><span class="n">a</span>
</pre></div>
<p>Eu uso um script rodando nos switchs e gateway da rede que me enviam mensagens assim que ips duplicados são encontrados na rede.
Também da pra usar o <strong>arping</strong> do scpay para fazer as requisições arp e coletar os responses.</p>
<p>Abraços.</p>
2018-05-15T13:24:00+00:00
Silvio Ap Silva
-
Lauro Moura: C++ para Pythonistas – Introdução e iteradores
C++ pode ter a fama de ser uma linguagem com complexidade comparável à legislação tributária brasileira, mas ao mesmo tempo é uma linguagem extremamente poderosa, podendo ser usada tanto desde microcontroladores até super computadores, sem contar satélites espaciais. Tamanha complexidade pode assustar quem vem de linguagens mais simples como Python (malloc? rvalue references?). Uma ajuda … <a class="more-link" href="https://lauro.wordpress.com/2018/04/30/c-para-pythonistas-introducao-e-iteradores/">Continuar lendo <span class="meta-nav">→</span></a>
2018-05-01T02:10:28+00:00
-
Magnun Leno: Algumas Razões Para Amar o PostgresSQL
https://mindbending.org/pt/algumas-razoes-para-amar-o-postgressql
<p>Geralmente, quando se fala em SGBDs OpenSource, a primeira resposta que se ouve é MySQL/MariaDB. Eu sempre torço meu nariz para respostas como essa… Implicancia pessoal? Talvez um pouco, mas existem <a class="reference external" href="https://www.cybertec-postgresql.com/en/why-favor-postgresql-over-mariadb-mysql/" target="_blank">muitos fundamentos</a>…</p>
<p><a href="https://mindbending.org/pt/algumas-razoes-para-amar-o-postgressql">Algumas Razões Para Amar o PostgresSQL</a> é um artigo original de <a href="https://mindbending.org/pt">Mind Bending</a></p>
2018-04-19T18:02:00+00:00
-
Python Help: Preservando a ordem de sequências ao remover duplicatas
Imagine que você tenha uma lista com as URLs extraídas de uma página web e queira eliminar as duplicatas da mesma. Transformar a lista em um conjunto (set) talvez seja a forma mais comum de se fazer isso. Tipo assim: Porém, observe que perdemos a ordem original dos elementos. Esse é um efeito colateral indesejado […]
2018-02-26T01:31:38+00:00
-
PythonClub: Django Rest Framework - #3 Class Based Views
https://pythonclub.com.br/django-rest-framework-class-based-views.html
<ul>
<li>0 - <a href="https://pythonclub.com.br/django-rest-framework-quickstart.html">Quickstart</a></li>
<li>1 - <a href="https://pythonclub.com.br/django-rest-framework-serialization.html">Serialization</a></li>
<li>2 - <a href="https://pythonclub.com.br/django-rest-framework-requests-responses.html">Requests & Responses</a></li>
<li>3 - <strong>Class based views</strong></li>
</ul>
<p>Este post é continuação do post <a href="https://pythonclub.com.br/django-rest-framework-requests-responses.html">Django Rest Framework Requests & Responses</a>.</p>
<p>Finalmente chegamos as views baseadas em classes. A grande vantagem é que com poucas linhas de código já temos nossa API pronta.</p>
<p>Veja como fica a <a href="https://github.com/rg3915/drf/blob/b0aa989ffc756e6dc5f65e172dfb43d47127d743/core/views.py">views.py</a>:</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">django.http</span> <span class="kn">import</span> <span class="n">Http404</span>
<span class="kn">from</span> <span class="nn">rest_framework.views</span> <span class="kn">import</span> <span class="n">APIView</span>
<span class="kn">from</span> <span class="nn">rest_framework.response</span> <span class="kn">import</span> <span class="n">Response</span>
<span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">status</span>
<span class="kn">from</span> <span class="nn">core.models</span> <span class="kn">import</span> <span class="n">Person</span>
<span class="kn">from</span> <span class="nn">core.serializers</span> <span class="kn">import</span> <span class="n">PersonSerializer</span>
<span class="k">class</span> <span class="nc">PersonList</span><span class="p">(</span><span class="n">APIView</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> List all persons, or create a new person.</span>
<span class="sd"> """</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="n">persons</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">PersonSerializer</span><span class="p">(</span><span class="n">persons</span><span class="p">,</span> <span class="n">many</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">post</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">PersonSerializer</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">serializer</span><span class="o">.</span><span class="n">is_valid</span><span class="p">():</span>
<span class="n">serializer</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_201_CREATED</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_400_BAD_REQUEST</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">PersonDetail</span><span class="p">(</span><span class="n">APIView</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Retrieve, update or delete a person instance.</span>
<span class="sd"> """</span>
<span class="k">def</span> <span class="nf">get_object</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">pk</span><span class="p">):</span>
<span class="k">try</span><span class="p">:</span>
<span class="k">return</span> <span class="n">Person</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="n">pk</span><span class="p">)</span>
<span class="k">except</span> <span class="n">Person</span><span class="o">.</span><span class="n">DoesNotExist</span><span class="p">:</span>
<span class="k">raise</span> <span class="n">Http404</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">pk</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="n">person</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_object</span><span class="p">(</span><span class="n">pk</span><span class="p">)</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">PersonSerializer</span><span class="p">(</span><span class="n">person</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">put</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">pk</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="n">person</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_object</span><span class="p">(</span><span class="n">pk</span><span class="p">)</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">PersonSerializer</span><span class="p">(</span><span class="n">person</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">serializer</span><span class="o">.</span><span class="n">is_valid</span><span class="p">():</span>
<span class="n">serializer</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_400_BAD_REQUEST</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">delete</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="n">pk</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="n">person</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">get_object</span><span class="p">(</span><span class="n">pk</span><span class="p">)</span>
<span class="n">person</span><span class="o">.</span><span class="n">delete</span><span class="p">()</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_204_NO_CONTENT</span><span class="p">)</span>
</pre></div>
<p>E <code>urls.py</code>:</p>
<div class="highlight"><pre><span></span><span class="n">urlpatterns</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">path</span><span class="p">(</span><span class="s1">'persons/'</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">PersonList</span><span class="o">.</span><span class="n">as_view</span><span class="p">()),</span>
<span class="n">path</span><span class="p">(</span><span class="s1">'persons/<int:pk>/'</span><span class="p">,</span> <span class="n">views</span><span class="o">.</span><span class="n">PersonDetail</span><span class="o">.</span><span class="n">as_view</span><span class="p">()),</span>
<span class="p">]</span>
</pre></div>
<h2>Usando Mixins</h2>
<p>Repare que no exemplo anterior tivemos que definir os métodos <code>get()</code>, <code>post()</code>, <code>put()</code> e <code>delete()</code>. Podemos reduzir ainda mais esse código com o uso de mixins.</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">mixins</span>
<span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">generics</span>
<span class="kn">from</span> <span class="nn">core.models</span> <span class="kn">import</span> <span class="n">Person</span>
<span class="kn">from</span> <span class="nn">core.serializers</span> <span class="kn">import</span> <span class="n">PersonSerializer</span>
<span class="k">class</span> <span class="nc">PersonList</span><span class="p">(</span><span class="n">mixins</span><span class="o">.</span><span class="n">ListModelMixin</span><span class="p">,</span>
<span class="n">mixins</span><span class="o">.</span><span class="n">CreateModelMixin</span><span class="p">,</span>
<span class="n">generics</span><span class="o">.</span><span class="n">GenericAPIView</span><span class="p">):</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">PersonSerializer</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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="bp">self</span><span class="o">.</span><span class="n">list</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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="bp">self</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">PersonDetail</span><span class="p">(</span><span class="n">mixins</span><span class="o">.</span><span class="n">RetrieveModelMixin</span><span class="p">,</span>
<span class="n">mixins</span><span class="o">.</span><span class="n">UpdateModelMixin</span><span class="p">,</span>
<span class="n">mixins</span><span class="o">.</span><span class="n">DestroyModelMixin</span><span class="p">,</span>
<span class="n">generics</span><span class="o">.</span><span class="n">GenericAPIView</span><span class="p">):</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">PersonSerializer</span>
<span class="k">def</span> <span class="nf">get</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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="bp">self</span><span class="o">.</span><span class="n">retrieve</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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="bp">self</span><span class="o">.</span><span class="n">update</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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="bp">self</span><span class="p">,</span> <span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</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="bp">self</span><span class="o">.</span><span class="n">destroy</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
</pre></div>
<h2>Usando generic class-based views</h2>
<p>E para finalizar usamos <code>ListCreateAPIView</code> e <code>RetrieveUpdateDestroyAPIView</code> que já tem todos os métodos embutidos.</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">generics</span>
<span class="kn">from</span> <span class="nn">core.models</span> <span class="kn">import</span> <span class="n">Person</span>
<span class="kn">from</span> <span class="nn">core.serializers</span> <span class="kn">import</span> <span class="n">PersonSerializer</span>
<span class="k">class</span> <span class="nc">PersonList</span><span class="p">(</span><span class="n">generics</span><span class="o">.</span><span class="n">ListCreateAPIView</span><span class="p">):</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">PersonSerializer</span>
<span class="k">class</span> <span class="nc">PersonDetail</span><span class="p">(</span><span class="n">generics</span><span class="o">.</span><span class="n">RetrieveUpdateDestroyAPIView</span><span class="p">):</span>
<span class="n">queryset</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer_class</span> <span class="o">=</span> <span class="n">PersonSerializer</span>
</pre></div>
<p>Versão final de <a href="https://github.com/rg3915/drf/blob/263ca63e5c8e2dd2e0f5d6c88c5733fcfdab4f74/core/views.py">views.py</a>.</p>
<p>Abraços.</p>
2018-02-16T01:00:00+00:00
Regis da Silva
-
PythonClub: Django Rest Framework - #2 Requests and Responses
https://pythonclub.com.br/django-rest-framework-requests-responses.html
<ul>
<li>0 - <a href="https://pythonclub.com.br/django-rest-framework-quickstart.html">Quickstart</a></li>
<li>1 - <a href="https://pythonclub.com.br/django-rest-framework-serialization.html">Serialization</a></li>
<li>2 - <strong>Requests & Responses</strong></li>
<li>3 - <a href="https://pythonclub.com.br/django-rest-framework-class-based-views.html">Class based views</a></li>
</ul>
<p>Este post é continuação do post <a href="https://pythonclub.com.br/django-rest-framework-serialization.html">Django Rest Framework Serialization</a>.</p>
<p>O uso de <em>requests</em> e <em>responses</em> torna nossa api mais flexível. A funcionalidade principal do objeto <strong>Request</strong> é o atributo <code>request.data</code>, que é semelhante ao <code>request.POST</code>, mas é mais útil para trabalhar com APIs.</p>
<h2>Objeto Response</h2>
<p>Introduzimos aqui um objeto <code>Response</code>, que é um tipo de <code>TemplateResponse</code> que leva conteúdo não renderizado e usa a negociação de conteúdo para determinar o tipo de conteúdo correto para retornar ao cliente.</p>
<div class="highlight"><pre><span></span><span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">data</span><span class="p">)</span> <span class="c1"># Renderiza para o tipo de conteúdo conforme solicitado pelo cliente.</span>
</pre></div>
<p>Repare também no uso de <em>status code</em> pré definidos, exemplo: <code>status.HTTP_400_BAD_REQUEST</code>.</p>
<p>E usamos o decorador <code>@api_view</code> para trabalhar com funções. Ou <code>APIView</code> para classes.</p>
<p>Nosso código ficou assim:</p>
<div class="highlight"><pre><span></span><span class="c1"># views.py</span>
<span class="kn">from</span> <span class="nn">rest_framework</span> <span class="kn">import</span> <span class="n">status</span>
<span class="kn">from</span> <span class="nn">rest_framework.decorators</span> <span class="kn">import</span> <span class="n">api_view</span>
<span class="kn">from</span> <span class="nn">rest_framework.response</span> <span class="kn">import</span> <span class="n">Response</span>
<span class="kn">from</span> <span class="nn">core.models</span> <span class="kn">import</span> <span class="n">Person</span>
<span class="kn">from</span> <span class="nn">core.serializers</span> <span class="kn">import</span> <span class="n">PersonSerializer</span>
<span class="nd">@api_view</span><span class="p">([</span><span class="s1">'GET'</span><span class="p">,</span> <span class="s1">'POST'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">person_list</span><span class="p">(</span><span class="n">request</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> List all persons, or create a new person.</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">'GET'</span><span class="p">:</span>
<span class="n">persons</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">all</span><span class="p">()</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">PersonSerializer</span><span class="p">(</span><span class="n">persons</span><span class="p">,</span> <span class="n">many</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">'POST'</span><span class="p">:</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">PersonSerializer</span><span class="p">(</span><span class="n">data</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">serializer</span><span class="o">.</span><span class="n">is_valid</span><span class="p">():</span>
<span class="n">serializer</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_201_CREATED</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_400_BAD_REQUEST</span><span class="p">)</span>
<span class="nd">@api_view</span><span class="p">([</span><span class="s1">'GET'</span><span class="p">,</span> <span class="s1">'PUT'</span><span class="p">,</span> <span class="s1">'DELETE'</span><span class="p">])</span>
<span class="k">def</span> <span class="nf">person_detail</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">pk</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Retrieve, update or delete a person instance.</span>
<span class="sd"> """</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">person</span> <span class="o">=</span> <span class="n">Person</span><span class="o">.</span><span class="n">objects</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">pk</span><span class="o">=</span><span class="n">pk</span><span class="p">)</span>
<span class="k">except</span> <span class="n">Person</span><span class="o">.</span><span class="n">DoesNotExist</span><span class="p">:</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_404_NOT_FOUND</span><span class="p">)</span>
<span class="k">if</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">'GET'</span><span class="p">:</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">PersonSerializer</span><span class="p">(</span><span class="n">person</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">'PUT'</span><span class="p">:</span>
<span class="n">serializer</span> <span class="o">=</span> <span class="n">PersonSerializer</span><span class="p">(</span><span class="n">person</span><span class="p">,</span> <span class="n">data</span><span class="o">=</span><span class="n">request</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">if</span> <span class="n">serializer</span><span class="o">.</span><span class="n">is_valid</span><span class="p">():</span>
<span class="n">serializer</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">data</span><span class="p">)</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">serializer</span><span class="o">.</span><span class="n">errors</span><span class="p">,</span> <span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_400_BAD_REQUEST</span><span class="p">)</span>
<span class="k">elif</span> <span class="n">request</span><span class="o">.</span><span class="n">method</span> <span class="o">==</span> <span class="s1">'DELETE'</span><span class="p">:</span>
<span class="n">person</span><span class="o">.</span><span class="n">delete</span><span class="p">()</span>
<span class="k">return</span> <span class="n">Response</span><span class="p">(</span><span class="n">status</span><span class="o">=</span><span class="n">status</span><span class="o">.</span><span class="n">HTTP_204_NO_CONTENT</span><span class="p">)</span>
</pre></div>
<p>Veja no <a href="https://github.com/rg3915/drf/commit/69205da9262415eaf83ff04f22a635e912880a60">GitHub</a>.</p>
<h2>Usando sufixo opcional</h2>
<p>Em <code>core/urls.py</code> acrescente</p>
<div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">rest_framework.urlpatterns</span> <span class="kn">import</span> <span class="n">format_suffix_patterns</span>
<span class="o">...</span>
<span class="n">urlpatterns</span> <span class="o">=</span> <span class="n">format_suffix_patterns</span><span class="p">(</span><span class="n">urlpatterns</span><span class="p">)</span>
</pre></div>
<p>E em <code>views.py</code> acrescente <code>format=None</code> como parâmetro das funções a seguir:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">person_list</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
<span class="k">def</span> <span class="nf">person_detail</span><span class="p">(</span><span class="n">request</span><span class="p">,</span> <span class="n">pk</span><span class="p">,</span> <span class="nb">format</span><span class="o">=</span><span class="kc">None</span><span class="p">):</span>
</pre></div>
<p>Com isso você pode chamar a api da seguinte forma:</p>
<div class="highlight"><pre><span></span>http https://127.0.0.1:8000/persons.json <span class="c1"># ou</span>
http https://127.0.0.1:8000/persons.api
</pre></div>
<p>Até a próxima.</p>
2018-02-15T01:00:00+00:00
Regis da Silva
-
PythonClub: Programação funcional com Python #2 - Iteraveis e iteradores
https://pythonclub.com.br/progrmacao-funcional-com-python-2.html
<h1>2. Iteráveis e iteradores</h1>
<p>O que são iteráveis? Basicamente e a grosso modo, iteráveis em python são todos os objetos que implementam o método <code>__getitem__</code> ou <code>__iter__</code>. Beleza, vamos partir do simples.</p>
<p>Quase todos os tipos de dados em python são iteráveis, por exemplo: listas, strings, tuplas, dicionários, conjuntos, etc...</p>
<p>Vamos aos exemplos, eles são sempre mágicos:</p>
<div class="highlight"><pre><span></span><span class="n">lista</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="mi">5</span><span class="p">]</span>
<span class="c1"># iteração</span>
<span class="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">lista</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="c1"># 1</span>
<span class="c1"># 2</span>
<span class="c1"># 3</span>
<span class="c1"># 4</span>
<span class="c1"># 5</span>
</pre></div>
<p>Era só isso? Sim, nem doeu, fala a verdade.</p>
<p>Em python, o comando <code>for</code> nos fornece um iterador implícito. O que? Não entendi.</p>
<p>O laço <code>for</code> em python itera em cada elemento da sequência. Como no exemplo, o <code>for</code>, ou <code>foreach</code>, no caso vai passando por cada elemento da sequência. Não é necessária a implementação de um index como na linguagem C, onde a iteração é explícita:</p>
<div class="highlight"><pre><span></span><span class="k">for</span> <span class="p">(</span><span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o">></span> <span class="mi">10</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">){</span>
<span class="n">sequencia</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="p">}</span>
</pre></div>
<h2>2.1 <code>__getitem__</code></h2>
<p>O padrão de projeto iterator em python já vem implementado por padrão, como já foi dito antes. Basta que um objeto tenha os métodos <code>__iter__</code> ou <code>__getitem__</code> para que um laço possa ser utilizado.</p>
<p>Vamos exemplificar:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">iteravel</span><span class="p">:</span>
<span class="sd">"""</span>
<span class="sd"> Um objeto que implementa o `__getitem__` pode ser acessado por posição</span>
<span class="sd"> """</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">sequencia</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">seq</span> <span class="o">=</span> <span class="n">sequencia</span>
<span class="k">def</span> <span class="fm">__getitem__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">posicao</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Por exemplo, quando tentamos acessar um elemento da sequência usando</span>
<span class="sd"> slice:</span>
<span class="sd"> >>> iteravel[2]</span>
<span class="sd"> O interpretador python chama o __getitem__ do objeto e nos retorna</span>
<span class="sd"> a posição solicitada</span>
<span class="sd"> um exemplo:</span>
<span class="sd"> IN: lista = [1, 2, 3, 4]</span>
<span class="sd"> IN: lista[0]</span>
<span class="sd"> OUT: 1</span>
<span class="sd"> """</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">seq</span><span class="p">[</span><span class="n">posicao</span><span class="p">]</span>
</pre></div>
<p>Então, pode-se compreender, sendo bem rústico, que todos os objetos que implementam <code>__getitem__</code> são iteráveis em python.</p>
<h2>2.2 <code>__iter__</code></h2>
<p>Agora os objetos que implementam <code>__iter__</code> tem algumas peculiaridades. Por exemplo, quando o iterável (vamos pensar no <code>for</code>) chamar a sequência, ela vai pedir o <code>__iter__</code> que vai retornar uma instância de si mesmo para o <code>for</code> e ele vai chamar o <code>__next__</code> até que a exceção <code>StopIteration</code> aconteça.</p>
<p>Uma classe que implementa <code>__iter__</code>:</p>
<div class="highlight"><pre><span></span><span class="k">class</span> <span class="nc">iteravel</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">sequencia</span><span class="p">):</span>
<span class="bp">self</span><span class="o">.</span><span class="n">data</span> <span class="o">=</span> <span class="n">sequencia</span>
<span class="k">def</span> <span class="fm">__next__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Neste caso, como o método pop do objeto data é chamado</span>
<span class="sd"> (vamos pensar em uma lista) ele vai retornar o primeiro elemento</span>
<span class="sd"> (o de index 0) e vai remove-lo da lista</span>
<span class="sd"> E quando a sequência estiver vazia ele vai nos retornar um StopIteration</span>
<span class="sd"> O que vai fazer com que a iteração acabe.</span>
<span class="sd"> Porém, estamos iterando com pop, o que faz a nossa iteração ser a única,</span>
<span class="sd"> pois ela não pode ser repetida, dado que os elementos foram</span>
<span class="sd"> removidos da lista</span>
<span class="sd"> """</span>
<span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">sequencia</span><span class="p">:</span>
<span class="k">raise</span> <span class="ne">StopIteration</span>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">sequencia</span><span class="o">.</span><span class="n">pop</span><span class="p">()</span>
<span class="k">def</span> <span class="fm">__iter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
<span class="k">return</span> <span class="bp">self</span>
</pre></div>
<p>Como é possível notar, o objeto com <code>__iter__</code> não necessita de um <code>__getitem__</code> e vise-versa. As diferenças partem do ponto em que um pode ser acessado por index/slice e outro não. Também um bom ponto é que nesse nosso caso, removemos os elementos da sequência, ou seja, ela se torna descartável.</p>
<p>Esse conceito, de ser descartado, pode parecer um pouco estranho no início, mas economiza muita memória. E, como estamos falando de programação funcional, pode-se dizer que nossa sequência se torna imutável, pois não existe uma maneira de mudar os valores contidos na lista do objeto.</p>
<p>Seguem dois links maravilhosos explicando sobre iteração em python:</p>
<ul>
<li>
<p><a href="https://www.python.org/dev/peps/pep-0234/">PEP0243</a></p>
</li>
<li>
<p><a href="https://www.youtube.com/watch?v=ULj7ejvuzI8">Iteração em Python: do básico ao genial</a></p>
</li>
</ul>
<p>O primeiro é a PEP sobre as estruturas dos iteráveis e o segundo um video do Guru Luciano Ramalho explicando tudo sobre iteradores.</p>
<p>Ah... Ia quase me esquecendo, se você não entendeu muita coisa sobre os dunders, você pode ler o <a href="https://docs.python.org/3/reference/datamodel.html#special-method-names">Python data model</a>. Obs: não me responsabilizo pelo programador melhor que você sairá desta página.</p>
<p>Embora esse tópico seja talvez o mais curto, ele vai ser de fundamental importância para o entendimento de um pouco de tudo nesse 'Curso'. É sério. Vamos entender como trabalhar com iteráveis de uma maneira bonita no próximo tópico.</p>
2017-12-24T00:50:00+00:00
Eduardo Mendes
-
PythonClub: Programação funcional com Python #1 - Funções
https://pythonclub.com.br/progrmacao-funcional-com-python-1.html
<h1>1. Funções</h1>
<p>Como nem tudo são flores, vamos começar do começo e entender algumas características das funções do python (o objeto função) e dar uma revisada básica em alguns conceitos de função só pra gente não se perder no básico depois. Então o primeiro tópico vai se limitar a falar da estrutura básica das funções em python, sem entrar profundamente em cada um dos tópicos. Será uma explanação de código e abrir a cabeça para novas oportunidades de código mais pythonicos e que preferencialmente gere menos efeito colateral. Mas calma, não vamos ensinar a fazer funções, você já está cheio disso.</p>
<h2>1.1 Funções como objeto de primeira classe</h2>
<p>Funções como objeto de primeira classe, são funções que se comportam como qualquer tipo nativo de uma determinada linguagem. Por exemplo:</p>
<div class="highlight"><pre><span></span><span class="c1"># uma lista</span>
<span class="n">lista</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'str'</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="p">(</span><span class="mi">1</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="p">{</span><span class="mi">1</span><span class="p">:</span> <span class="s1">'um'</span><span class="p">}]</span>
</pre></div>
<p>Todos esses exemplos são tipos de objetos de primeira classe em Python, mas no caso as funções também são. Como assim? Pode-se passar funções como parâmetro de uma outra função, podemos armazenar funções em variáveis, pode-se definir funções em estruturas de dados:</p>
<div class="highlight"><pre><span></span><span class="c1"># Funções como objeto de primeira classe</span>
<span class="n">func</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span> <span class="c1"># a função anônima, lambda, foi armazenada em uma variável</span>
<span class="k">def</span> <span class="nf">func_2</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">lista</span> <span class="o">=</span> <span class="p">[</span><span class="n">func</span><span class="p">,</span> <span class="n">func_2</span><span class="p">]</span> <span class="c1"># a variável que armazena a função foi inserida em uma estrutura, assim como uma função gerada com def</span>
<span class="n">lista_2</span> <span class="o">=</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="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="c1"># aqui as funções foram definidas dentro de uma estrutura</span>
</pre></div>
<p>Como é possível notar, em python, as funções podem ser inseridas em qualquer contexto e também geradas em tempo de execução. Com isso nós podemos, além de inserir funções em estruturas, retornar funções, passar funções como parâmetro (HOFs), definir funções dentro de funções(closures) e assim por diante. Caso você tenha aprendido a programar usando uma linguagem em que as funções não são objetos de primeira classe, não se assuste. Isso faz parte da rotina comum do python. Preferencialmente, e quase obrigatoriamente, é melhor fazer funções simples, pequenas e de pouca complexidade para que elas não sofram interferência do meio externo, gerem menos manutenção e o melhor de tudo, possam ser combinadas em outras funções. Então vamos lá!</p>
<h2>1.2 Funções puras</h2>
<p>Funções puras são funções que não sofrem interferência do meio externo. Vamos começar pelo exemplo ruim:</p>
<div class="highlight"><pre><span></span><span class="n">valor</span> <span class="o">=</span> <span class="mi">5</span>
<span class="k">def</span> <span class="nf">mais_cinco</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="n">valor</span>
<span class="k">assert</span> <span class="n">mais_cinco</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="o">==</span> <span class="mi">10</span> <span class="c1"># True</span>
<span class="n">valor</span> <span class="o">=</span> <span class="mi">7</span>
<span class="k">assert</span> <span class="n">mais_cinco</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="o">==</span> <span class="mi">10</span> <span class="c1"># AssertionError</span>
</pre></div>
<p><code>mais_cinco()</code> é o exemplo claro de uma função que gera efeito colateral. Uma função pura deve funcionar como uma caixa preta, todas as vezes em que o mesmo input for dado nela, ela terá que retornar o mesmo valor. Agora vamos usar o mesmo exemplo, só alterando a linha do return:</p>
<div class="highlight"><pre><span></span><span class="n">valor</span> <span class="o">=</span> <span class="mi">5</span>
<span class="k">def</span> <span class="nf">mais_cinco</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">5</span>
<span class="k">assert</span> <span class="n">mais_cinco</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="o">==</span> <span class="mi">10</span> <span class="c1"># True</span>
<span class="n">valor</span> <span class="o">=</span> <span class="mi">7</span>
<span class="k">assert</span> <span class="n">mais_cinco</span><span class="p">(</span><span class="mi">5</span><span class="p">)</span> <span class="o">==</span> <span class="mi">10</span> <span class="c1"># True</span>
</pre></div>
<p>Pode parecer trivial, mas muitas vezes por comodidade deixamos o meio influenciar no comportamento de uma função. Por definição o Python só faz possível, e vamos falar disso em outro tópico, a leitura de variáveis externas. Ou seja, dentro do contexto da função as variáveis externas não podem ser modificadas, mas isso não impede que o contexto externo a modifique. Se você for uma pessoa inteligente como o Jaber deve saber que nunca é uma boa ideia usar valores externos. Mas, caso seja necessário, você pode sobrescrever o valor de uma variável no contexto global usando a palavra reservada <code>global</code>. O que deve ficar com uma cara assim:</p>
<div class="highlight"><pre><span></span><span class="n">valor</span> <span class="o">=</span> <span class="mi">5</span>
<span class="k">def</span> <span class="nf">teste</span><span class="p">():</span>
<span class="k">global</span> <span class="n">valor</span> <span class="c1"># aqui é feita a definição</span>
<span class="n">valor</span> <span class="o">=</span> <span class="mi">7</span>
<span class="nb">print</span><span class="p">(</span><span class="n">valor</span><span class="p">)</span> <span class="c1"># 7</span>
</pre></div>
<p>Só lembre-se de ser sempre coerente quando fizer isso, as consequências podem ser imprevisíveis. Nessa linha de funções puras e pequeninas, podemos caracterizar, embora isso não as defina, funções de ordem superior, que são funções que recebem uma função como argumento, ou as devolvem, e fazem a chamada das mesmas dentro do contexto da função que a recebeu como parâmetro. Isso resulta em uma composição de funções, o que agrega muito mais valor caso as funções não gerem efeitos colaterais.</p>
<h2>1.3 Funções de ordem superior (HOFs)</h2>
<p>Funções de ordem superior são funções que recebem funções como argumento(s) e/ou retornam funções como resposta. Existem muitas funções embutidas em python de ordem superior, como: <code>map, filter, zip</code> e praticamente todo o módulo functools <code>import functools</code>. Porém, nada impede de criarmos novas funções de ordem superior. Um ponto a ser lembrado é que map e filter não tem mais a devida importância em python com a entrada das comprehensions (embora eu as adore), o que nos faz escolher única e exclusivamente por gosto, apesar de comprehensions serem mais legíveis (vamos falar disso em outro contexto), existem muitos casos onde elas ainda fazem sentido. Mas sem me estender muito, vamos ao código:</p>
<div class="highlight"><pre><span></span><span class="n">func</span> <span class="o">=</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">2</span> <span class="c1"># uma função simples, soma mais 2 a qualquer inteiro</span>
<span class="k">def</span> <span class="nf">func_mais_2</span><span class="p">(</span><span class="n">funcao</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Executa a função passada por parâmetro e retorna esse valor somado com dois</span>
<span class="sd"> Ou seja, é uma composição de funções:</span>
<span class="sd"> Dado que func(valor) é processado por func_func:</span>
<span class="sd"> func_mais_2(func(valor)) == f(g(x))</span>
<span class="sd"> """</span>
<span class="k">return</span> <span class="n">funcao</span><span class="p">(</span><span class="n">valor</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span>
</pre></div>
<p>Um ponto a tocar, e o que eu acho mais bonito, é que a função vai retornar diferentes respostas para o mesmo valor, variando a entrada da função. Nesse caso, dada a entrada de um inteiro ele será somado com 2 e depois com mais dois. Mas, vamos estender este exemplo:</p>
<div class="highlight"><pre><span></span><span class="n">func</span> <span class="o">=</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">2</span> <span class="c1"># uma função simples, soma mais 2 a qualquer inteiro</span>
<span class="k">def</span> <span class="nf">func_mais_2</span><span class="p">(</span><span class="n">funcao</span><span class="p">,</span> <span class="n">valor</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Função que usamos antes.</span>
<span class="sd"> """</span>
<span class="k">return</span> <span class="n">funcao</span><span class="p">(</span><span class="n">valor</span><span class="p">)</span> <span class="o">+</span> <span class="mi">2</span>
<span class="k">assert</span> <span class="n">func_mais_2</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="o">==</span> <span class="mi">6</span> <span class="c1"># true</span>
<span class="k">def</span> <span class="nf">func_quadrada</span><span class="p">(</span><span class="n">val</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Eleva o valor de entrada ao quadrado.</span>
<span class="sd"> """</span>
<span class="k">return</span> <span class="n">val</span> <span class="o">*</span> <span class="n">val</span>
<span class="k">assert</span> <span class="n">func_mais_2</span><span class="p">(</span><span class="n">func_quadrada</span><span class="p">,</span> <span class="mi">2</span><span class="p">)</span> <span class="o">==</span> <span class="mi">6</span> <span class="c1"># true</span>
</pre></div>
<h3>1.3.1 Um exemplo usando funções embutidas:</h3>
<p>Muitas das funções embutidas em python são funções de ordem superior (HOFs) como a função map, que é uma das minhas preferidas. Uma função de map recebe uma função, que recebe um único argumento e devolve para nós uma nova lista com a função aplicada a cada elemento da lista:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">func</span><span class="p">(</span><span class="n">arg</span><span class="p">):</span>
<span class="k">return</span> <span class="n">arg</span> <span class="o">+</span> <span class="mi">2</span>
<span class="n">lista</span> <span class="o">=</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="mi">0</span><span class="p">]</span>
<span class="nb">list</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="n">func</span><span class="p">,</span> <span class="n">lista</span><span class="p">))</span> <span class="o">==</span> <span class="p">[</span><span class="mi">4</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="c1"># true</span>
</pre></div>
<p>Mas fique tranquilo, falaremos muito mais sobre isso.</p>
<h2>1.4 <code>__call__</code></h2>
<p>Por que falar de classes? Lembre-se, Python é uma linguagem construída em classes, e todos os objetos que podem ser chamados/invocados implementam o método <code>__call__</code>:</p>
<p>Em uma função anônima:</p>
<div class="highlight"><pre><span></span><span class="n">func</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span>
<span class="s1">'__call__'</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="n">func</span><span class="p">)</span> <span class="c1"># True</span>
</pre></div>
<p>Em funções tradicionais:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">func</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="s1">'__call__'</span> <span class="ow">in</span> <span class="nb">dir</span><span class="p">(</span><span class="n">func</span><span class="p">)</span> <span class="c1"># True</span>
</pre></div>
<p>Isso quer dizer que podemos gerar classes que se comportam como funções?</p>
<p>SIIIIIM. Chupa Haskell</p>
<p>Essa é uma parte interessante da estrutura de criação do Python a qual veremos mais em outro momento sobre introspecção de funções, mas vale a pena dizer que classes, funções nomeadas, funções anônimas e funções geradoras usam uma base comum para funcionarem, essa é uma das coisas mais bonitas em python e que em certo ponto fere a ortogonalidade da linguagem, pois coisas iguais tem funcionamentos diferentes, mas facilita o aprendizado da linguagem, mas não é nosso foco agora.</p>
<h2>1.5 Funções geradoras</h2>
<p>Embora faremos um tópico extremamente focado em funções geradoras, não custa nada dar uma palinha, não?</p>
<p>Funções geradoras são funções que nos retornam um iterável. Mas ele é lazy (só é computado quando invocado). Para exemplo de uso, muitos conceitos precisam ser esclarecidos antes de entendermos profundamente o que acontece com elas, mas digo logo: são funções lindas <3</p>
<p>Para que uma função seja geradora, em tese, só precisamos trocar o return por yield:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">gen</span><span class="p">(</span><span class="n">lista</span><span class="p">):</span>
<span class="k">for</span> <span class="n">elemento</span> <span class="ow">in</span> <span class="n">lista</span><span class="p">:</span>
<span class="k">yield</span> <span class="n">elemento</span>
<span class="n">gerador</span> <span class="o">=</span> <span class="n">gen</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="nb">next</span><span class="p">(</span><span class="n">gerador</span><span class="p">)</span> <span class="c1"># 1</span>
<span class="nb">next</span><span class="p">(</span><span class="n">gerador</span><span class="p">)</span> <span class="c1"># 2</span>
<span class="nb">next</span><span class="p">(</span><span class="n">gerador</span><span class="p">)</span> <span class="c1"># 3</span>
<span class="nb">next</span><span class="p">(</span><span class="n">gerador</span><span class="p">)</span> <span class="c1"># 4</span>
<span class="nb">next</span><span class="p">(</span><span class="n">gerador</span><span class="p">)</span> <span class="c1"># 5</span>
<span class="nb">next</span><span class="p">(</span><span class="n">gerador</span><span class="p">)</span> <span class="c1"># StopIteration</span>
</pre></div>
<p>Passando bem por cima, uma função geradora nos retorna um iterável que é preguiçoso. Ou seja, ele só vai efetuar a computação quando for chamado.</p>
<h2>1.6 Funções anônimas (lambda)</h2>
<p>Funções anônimas, ou funções lambda, são funções que podem ser declaradas em qualquer contexto. Tá... Todo tipo de função em python pode ser declarada em tempo de execução. Porém funções anônimas podem ser atribuídas a variáveis, podem ser definidas dentro de sequências e declaradas em um argumento de função. Vamos olhar sua sintaxe:</p>
<div class="highlight"><pre><span></span><span class="k">lambda</span> <span class="n">argumento</span><span class="p">:</span> <span class="n">argumento</span>
</pre></div>
<p>A palavra reservada <code>lambda</code> define a função, assim como uma <code>def</code>. Porém em uma <code>def</code> quase que instintivamente sempre quebramos linha:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">func</span><span class="p">():</span>
<span class="k">pass</span>
</pre></div>
<p>Uma das diferenças triviais em python é que as funções anônimas não tem nome. Tá, isso era meio óbvio, mas vamos averiguar:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">func</span><span class="p">():</span>
<span class="k">pass</span>
<span class="n">func</span><span class="o">.</span><span class="vm">__name__</span> <span class="c1"># func</span>
<span class="n">lambda_func</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">arg</span><span class="p">:</span> <span class="n">arg</span>
<span class="n">lambda_func</span><span class="o">.</span><span class="vm">__name__</span> <span class="c1"># '<lambda>'</span>
</pre></div>
<p>O resultado <code>'<lambda>'</code> será o mesmo para qualquer função. Isso torna sua depuração praticamente impossível em python. Por isso os usuários de python (e nisso incluo todos os usuários, até aqueles que gostam de funcional) não encorajam o uso de funções lambda a todos os contextos da linguagem. Mas, em funções que aceitam outra funções isso é meio que uma tradição, caso a função (no caso a que executa o código a ser usado pelo lambda) não esteja definida e nem seja reaproveitada em outro contexto. Eu gosto de dizer que lambdas são muito funcionais em aplicações parciais de função. Porém, os lambdas não passam de açúcar sintático em Python, pois não há nada que uma função padrão (definida com <code>def</code>), não possa fazer de diferente. Até a introspecção retorna o mesmo resultado:</p>
<div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">func</span><span class="p">():</span>
<span class="k">pass</span>
<span class="nb">type</span><span class="p">(</span><span class="n">func</span><span class="p">)</span> <span class="c1"># function</span>
<span class="n">lambda_func</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">arg</span><span class="p">:</span> <span class="n">arg</span>
<span class="nb">type</span><span class="p">(</span><span class="n">lambda_func</span><span class="p">)</span> <span class="c1"># function</span>
</pre></div>
<p>Uma coisa que vale ser lembrada é que funções anônimas em python só executam uma expressão. Ou seja, não podemos usar laços de repetição (<code>while</code>, <code>for</code>), tratamento de exceções (<code>try</code>, <code>except</code>, <code>finally</code>). Um simples <code>if</code> com uso de <code>elif</code> também não pode ser definido. Como sintaticamente só são aceitas expressões, o único uso de um <code>if</code> é o ternário:</p>
<div class="highlight"><pre><span></span><span class="n">valor_1</span> <span class="k">if</span> <span class="n">condicao</span> <span class="k">else</span> <span class="n">valor_2</span>
</pre></div>
<p>O que dentro de um lambda teria essa aparência:</p>
<div class="highlight"><pre><span></span><span class="n">func</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">argumento</span><span class="p">:</span> <span class="n">argumento</span> <span class="o">+</span> <span class="mi">2</span> <span class="k">if</span> <span class="n">argumento</span> <span class="o">></span> <span class="mi">0</span> <span class="k">else</span> <span class="n">argumento</span> <span class="o">-</span> <span class="mi">2</span>
</pre></div>
<p>Funções lambda também podem ter múltiplos argumentos, embora seu processamento só possa ocorrer em uma expressão:</p>
<div class="highlight"><pre><span></span><span class="n">func</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">arg_1</span><span class="p">,</span> <span class="n">arg_2</span><span class="p">,</span> <span class="n">arg_3</span><span class="p">:</span> <span class="kc">True</span> <span class="k">if</span> <span class="nb">sum</span><span class="p">([</span><span class="n">arg_1</span><span class="p">,</span> <span class="n">arg_2</span><span class="p">,</span> <span class="n">arg_3</span><span class="p">])</span> <span class="o">></span> <span class="mi">7</span> <span class="k">else</span> <span class="nb">min</span><span class="p">([</span><span class="n">arg_1</span><span class="p">,</span> <span class="n">arg_2</span><span class="p">,</span> <span class="n">arg_3</span><span class="p">])</span>
</pre></div>
<p>Embora essa seja uma explanação inicial sobre as funções anônimas, grande parte dos tópicos fazem uso delas e vamos poder explorar melhor sua infinitude.</p>
<p>Mas por hoje é só e no tópico seguinte vamos discutir, mesmo que superficialmente, iteradores e iteráveis e suas relações com a programação funcional.</p>
2017-12-08T15:30:00+00:00
Eduardo Mendes
-
PythonClub: Programação funcional com Python #0 - Saindo da zona de conforto
https://pythonclub.com.br/progrmacao-funcional-com-python-0.html
<h1>0. Saindo da zona de conforto</h1>
<p>Sinta-se um vencedor, se você chegou até aqui, isso significa que quer aprender mais sobre o mundo da programação.</p>
<p>Aprender novos paradígmas podem te trazer muitas coisas positivas, assim como aprender linguagens diferentes, pois paradígmas e linguagens transpõem maneiras, estruturas e métodos de implementação completamente diferentes. Com isso você pode ter mais ferramentas para usar no dia a dia. Você pode aumentar sua capacidade de expressar ideias de diferentes maneiras. Eu penso que o maior limitador de um programador é a linguagem de programação em que ele tem domínio. Quando você aprende linguagens imperativas, como C, Python, Java e etc..., você se vê limitado ao escopo de criar e manipular variáveis. Não que isso seja uma coisa ruim, porém existem outras maneiras de resolver problemas e quando você tem conhecimento disso consegue avaliar melhor quando implementar cada tipo de coisa.</p>
<p>Você pode me dizer que aprender diferentes tipos de estruturas e maneiras de computar é uma coisa negativa pois tudo é variável nesse contexto. Mas eu penso exatamente o contrário, quanto mais você aprender da sua língua nativa, no caso estamos falando em português, maior o campo de domínio que você tem sobre como se comunicar e expressar ideias. Assim como aprender outras línguas te darão mais fundamentos para expressar ideias em outros idiomas, que não são melhores que os seu, mas diferentes e compõem diferentes estruturas, e isso pode ser libertador. Não quero me prolongar nesse assunto, mas dizer que isso pode acrescentar muito na suas habilidades cognitivas, até mesmo para usar ferramentas que você já usa no seu dia a dia.</p>
<p>Vamos começar fazendo uma tentativa de entender os paradígmas de programação, sem muito falatório e complicações. Um exemplo muito legal é do David Mertz em "Functional Programming in Python":</p>
<ul>
<li>
<p>Usa-se programação funcional quando se programa em Lisp, Haskell, Scala, Erlang, F# etc..</p>
</li>
<li>
<p>Do mesmo modo que se usa programação imperativa quando se programada C/C++, Pascal, Java, Python etc...</p>
</li>
<li>
<p>Também quando se programa Prolog estamos programando usando o paradígma lógico.</p>
</li>
</ul>
<p>Apesar de não ser uma definição muito elegante, talvez seja a melhor a ser dada em muitas ocasiões. Vamos tentar ser um pouco mais objetivos em relação ao estilo de computação, embora essa discussão não tenha fim:</p>
<ul>
<li>
<p>O foco de usar programação imperativa está no ato de mudar variáveis. A computação se dá pela modificação dos estados das variáveis iniciais. Sendo assim, vamos pensar que tudo é definido no início e vai se modificando até que o resultado esperado seja obtido.</p>
</li>
<li>
<p>Na programação funcional, se tem a noção de que o estado deve ser substituído, no caso da avaliação, para criação de um novo 'objeto' que no caso são funções.</p>
</li>
</ul>
<h2>0.1 Mas de onde vem a programação funcional?</h2>
<p>O florescer da programação funcional nasce no Lisp (acrônomo para List Processing) para tentar resolver alguns problemas de inteligência artificial que eram provenientes da linguística, que tinha foco em processamento de linguagem natural que por sua vez eram focados em processamento de listas em geral. Isso justifica uma grande parte do conteúdo que vamos ver aqui e seus tipos de dados variam somente entre listas e átomos. E assim foi mantido o foco de processamento de listas em todas as linguagens funcionais e suas funções e abstrações para resolver problemas relativos a listas e estruturas iteráveis. Uma curiosidade é que para quem não sabe porque em lisp existem tantos parênteses é que ele é baseado em s-expression, uma coisa que temos um "equivalente" evoluído em python, que parte dos teoremas de gramáticas livres de contexto:</p>
<div class="highlight"><pre><span></span><span class="p">(</span><span class="nb">+ </span><span class="mi">4</span> <span class="mi">5</span><span class="p">)</span>
</pre></div>
<p>Sim, isso é uma soma em lisp. Diferente das linguagens imperativas como costumamos ver:</p>
<div class="highlight"><pre><span></span><span class="mi">4</span> <span class="o">+</span> <span class="mi">5</span>
</pre></div>
<p>Uma assertiva pode ser feita dessa maneira:</p>
<ul>
<li>Funcional (ou declarativa)</li>
</ul>
<div class="highlight"><pre><span></span><span class="p">(</span><span class="nb">= </span><span class="mi">4</span> <span class="p">(</span><span class="nb">+ </span><span class="mi">2</span> <span class="mi">2</span><span class="p">))</span>
</pre></div>
<ul>
<li>Imperativa</li>
</ul>
<div class="highlight"><pre><span></span><span class="p">(</span><span class="mi">2</span> <span class="o">+</span> <span class="mi">2</span><span class="p">)</span> <span class="o">==</span> <span class="mi">4</span>
</pre></div>
<p>Chega de enrolação e vamos correr com essa introdução, não viemos aqui para aprender Lisp ou C. Mas acho que parte desse contexto pode nos ajudar e muito quando formos nos aprofundar em alguns tópicos. Pretendo sempre que iniciar uma nova ferramenta da programação funcional ao menos explicar em que contexto ela foi desenvolvida e para resolver cada tipo de problema.</p>
<h2>0.2 Técnicas usadas por linguagens funcionais</h2>
<p>Vamos tentar mapear o que as linguagens funcionais fazem de diferente das linguagens imperativas, mas não vamos nos aprofundar nesse tópicos agora, pois são coisas às vezes complexas sem o entendimento prévio de outros contextos, mas vamos tentar só explanar pra que você se sinta empolgado por estar aqui:</p>
<ul>
<li>
<p>Funções como objetos de primeira classe:</p>
<ul>
<li>São funções que podem estar em qualquer lugar (em estruturas, declaradas em tempo de execução).</li>
</ul>
</li>
<li>
<p>Funções de ordem superior:</p>
<ul>
<li>São funções que podem receber funções como argumentos e retornar funções.</li>
</ul>
</li>
<li>
<p>Funções puras:</p>
<ul>
<li>São funções que não sofrem interferências de meios externos (variáveis de fora). Evita efeitos colaterais.</li>
</ul>
</li>
<li>
<p>Recursão, como oposição aos loops:</p>
<ul>
<li>Frequentemente a recursão na matemática é uma coisa mais intuitiva e é só chamar tudo outra vez, no lugar de ficar voltando ao ponto inicial da iteração.</li>
</ul>
</li>
<li>
<p>Foco em processamento de iteráveis:</p>
<ul>
<li>Como dito anteriormente, pensar em como as sequências podem nos ajudar a resolver problemas.</li>
</ul>
</li>
<li>
<p>O que deve ser computado, não como computar:</p>
<ul>
<li>Não ser tão expressivo e aceitar que as intruções não tem necessidade de estar explicitas todas as vezes, isso ajuda em legibilidade.</li>
</ul>
</li>
<li>
<p>Lazy evaluation:</p>
<ul>
<li>Criar sequências infinitas sem estourar nossa memória.</li>
</ul>
</li>
</ul>
<h2>0.3 Python é uma linguagem funcional?</h2>
<h4>Não. Mas é uma linguagem que implementa muitos paradígmas e porque não usar todos de uma vez?</h4>
<p>O objetivo desse 'conjunto de vídeos' é escrever código que gere menos efeito colateral e código com menos estados. Só que isso tudo feito na medida do possível, pois Python não é uma linguagem funcional. Porém, podemos contar o máximo possível com as features presentes do paradígma em python.</p>
<p>Exemplos de funcional (básicos) em python:</p>
<div class="highlight"><pre><span></span><span class="c1"># Gerar uma lista da string # Imperativo</span>
<span class="n">string</span> <span class="o">=</span> <span class="s1">'Python'</span>
<span class="n">lista</span> <span class="o">=</span> <span class="p">[]</span> <span class="c1"># estado inicial</span>
<span class="k">for</span> <span class="n">l</span> <span class="ow">in</span> <span class="n">string</span><span class="p">:</span>
<span class="n">lista</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">l</span><span class="p">)</span> <span class="c1"># cada iteração gera um novo estado</span>
<span class="nb">print</span><span class="p">(</span><span class="n">lista</span><span class="p">)</span> <span class="c1"># ['P', 'y', 't', 'h', 'o', 'n']</span>
</pre></div>
<div class="highlight"><pre><span></span><span class="c1"># Gerar uma lista da string # Funcional</span>
<span class="n">string</span> <span class="o">=</span> <span class="k">lambda</span> <span class="n">x</span><span class="p">:</span> <span class="n">x</span>
<span class="n">lista</span> <span class="o">=</span> <span class="nb">list</span><span class="p">(</span><span class="nb">map</span><span class="p">(</span><span class="nb">str</span><span class="p">,</span> <span class="n">string</span><span class="p">(</span><span class="s1">'Python'</span><span class="p">)))</span> <span class="c1"># atribuição a um novo objeto</span>
<span class="nb">print</span><span class="p">(</span><span class="n">lista</span><span class="p">)</span> <span class="c1"># ['P', 'y', 't', 'h', 'o', 'n']</span>
</pre></div>
<p>Como você pode ver, depois de uma explanação básica das técnicas, a segunda implementação não sofre interferência do meio externo (Funções puras), evita loops e sua saída sem o construtor de list é lazy. Mas não se assuste, vamos abordar tudo isso com calma.</p>
<h2>0.4 A quem esse 'curso' é destinado?</h2>
<p>Primeiramente gostaria de dizer que roubei essa ideia dos infinitos livros da O’Reilly, que sempre exibem esse tópico. Mas vamos ao assunto. Este curso é para você que sabe o básico de Python, e quando digo básico quero dizer que consegue fazer qualquer coisa com um pouco de pesquisa na internet. O básico de programação se reduz a isso. Vamos falar sobre coisas simples e coisas mais complexas, mas pretendo manter o bom senso para que todos possam absorver o máximo de conteúdo possível.</p>
<p>Então, caso você venha do Python (OO ou procedural) você vai encontrar aqui uma introdução a programação funcional descontraída e sem uma tonelada de material difícil de entender. Caso você venha de linguagens funcionais como Haskell e Lisp, você pode se sentir um pouco estranho com tantas declarações, mas aprenderá a se expressar em Python. Caso você venha de linguagens funcionais modernas como Clojure e Scala, as coisas são bem parecidas por aqui.</p>
<p>Então tente tirar o máximo de proveito. Vamos nos divertir.</p>
<h2>0.5 Apresentando o Jaber</h2>
<p>Jaber é nosso aluno de mentira, mas vamos pensar que ele é um aluno que senta na primeira fileira e pergunta de tudo, sempre que acha necessário. Roubei essa ideia do livro de expressões regulares do Aurélio. Ele tem um personagem, Piazinho, e acho que toda interação com ele é sempre graciosa e tira dúvidas quando tudo parece impossível.</p>
<h2>0.6 Sobre as referências</h2>
<p>Não gosto muito de citar referências pois procurei não copiar texto dos livros, mas muita coisa contida neles serve de base para o entendimento de certos tópicos. Outro motivo é o nível de complexidade dos exemplos ou explicações que tentei reduzir ao máximo enquanto escrevia esses roteiros. Para um exemplo, você pode olhar o livro do Steven Lott, cheio de fórmulas e abstrações matemáticas que em certo ponto acabam comprometendo o entendimento de quem não tem uma sólida base em computação teórica ou matemática.</p>
<p>Como um todo, as referências serviram como guia, foi o que lí quando dúvidas para explicações surgiram. Não tiro nenhum crédito delas e as exponho para que todos saibam que existem muitos livros bons e que boa parte do que é passado aqui, foi aprendido neles.</p>
<h2>0.7 Mais sobre o histórico das linguagens funcionais</h2>
<p>Se você pretende realmente se aprofundar no assunto enquanto acompanha esse curso, fazer uma imersão ou coisa parecida. Tudo começa com o cálculo lambda mentalizado pelo incrível <a href="https://en.wikipedia.org/wiki/Alonzo_Church">Alonzo Church</a>. Caso você não o conheça, ele foi um matemático fantástico e teve uma carreira acadêmica brilhante. Foi o orientador de pessoas incríveis como Alan Turing, Raymond Smullyan etc...</p>
<p>Outro grande homem e que vale a pena mencionar e ser buscado é o <a href="https://pt.wikipedia.org/wiki/Haskell_Curry">Haskell Curry</a>, um lógico que trouxe excelentes contribuições para o que chamamos hoje de programação funcional.</p>
<p>A primeira linguagem funcional 'oficial' (não gosto muito de dizer isso) é o Lisp (List Processing) criada pelo fenomenal <a href="https://pt.wikipedia.org/wiki/John_McCarthy">John McCarthy</a> que também vale a pena ser pesquisado e estudado.</p>
<p>Veremos o básico sobre os tipos de função no próximo tópico.</p>
<p>OBS: <a href="https://github.com/z4r4tu5tr4/python-funcional/blob/master/referencias.md">Referências</a> usadas durante todos os tópicos.</p>
2017-11-20T21:43:00+00:00
Eduardo Mendes
-
PythonClub: Peewee - Um ORM Python minimalista
https://pythonclub.com.br/peewee-um-orm-python-minimalista.html
<p><a href="https://peewee.readthedocs.io/en/latest/index.html">Peewee</a> é um ORM destinado a criar e gerenciar tabelas de banco de dados relacionais através de objetos Python. Segundo a <a href="https://pt.wikipedia.org/wiki/Mapeamento_objeto-relacional">wikipedia</a>, um ORM é:</p>
<blockquote>
<p>Mapeamento objeto-relacional (ou ORM, do inglês: Object-relational mapping) é uma técnica de desenvolvimento > utilizada para reduzir a impedância da programação orientada aos objetos utilizando bancos de dados relacionais. As tabelas do banco de dados são representadas através de classes e os registros de cada tabela são representados como instâncias das classes correspondentes.</p>
</blockquote>
<p>O que um ORM faz é, basicamente, transformar classes Python em tabelas no banco de dados, além de permitir construir <em>querys</em> usando diretamente objetos Python ao invés de SQL.</p>
<p>O Peewee é destinado a projetos de pequeno/médio porte e se destaca pela simplicidade quando comparado a outros ORM mais conhecidos, como o SQLAlchemy. Uma analogia utilizada pelo autor da API e que acho muito interessante é que Peewee está para o SQLAlchemy assim como SQLite está para o PostgreSQL.</p>
<p>Em relação aos recursos por ele oferecidos, podemos citar que ele possui suporte nativo a SQLite, PostgreSQL e MySQL, embora seja necessário a instalação de <em>drivers</em> para utilizá-lo com PostgreSQL e MySQL e suporta tanto Python 2.6+ quanto Python 3.4+.</p>
<p>Neste tutorial, utilizaremos o SQLite, por sua simplicidade de uso e pelo Python possuir suporte nativo ao mesmo (usaremos o Python 3.5).</p>
<h2>Instalação</h2>
<p>O Peewee pode ser facilmente instalado com o gerenciador de pacotes <em>pip</em> (recomendo a instalação em um virtualenv):</p>
<div class="highlight"><pre><span></span><span class="err">pip install peewee</span>
</pre></div>
<h2>Criando o banco de dados</h2>
<p>Para criar o banco de dados é bem simples. Inicialmente passamos o nome do nosso banco de dados (a extensão <code>*.db</code> indica um arquivo do SQLite).</p>
<div class="highlight"><pre><span></span><span class="kn">import</span> <span class="nn">peewee</span>
<span class="c1"># Aqui criamos o banco de dados</span>
<span class="n">db</span> <span class="o">=</span> <span class="n">peewee</span><span class="o">.</span><span class="n">SqliteDatabase</span><span class="p">(</span><span class="s1">'codigo_avulso.db'</span><span class="p">)</span>
</pre></div>
<p>Diferente de outros bancos de dados que funcionam através um servidor, o SQLite cria um arquivo de extensão <code>*.db</code>, onde todos os nossos dados são armazenados.</p>
<blockquote>
<p>Caso deseje ver as tabelas existentes no arquivo <code>codigo_avulso.db</code>, instale o aplicativo <code>SQLiteBrowser</code>. Com ele fica fácil monitorar as tabelas criadas e acompanhar o tutorial.</p>
</blockquote>
<div class="highlight"><pre><span></span> sudo apt-get install sqlitebrowser
</pre></div>
<p>A título de exemplo, vamos criar um banco destinado a armazenar nomes de livros e de seus respectivos autores. Iremos chamá-lo de <code>models.py</code>.</p>
<p>Inicialmente, vamos criar a classe base para todos os nossos <code>models</code>. Esta é uma abordagem recomendada pela documentação e é considerada uma boa prática. Também adicionaremos um log para acompanharmos as mudanças que são feitas no banco:</p>
<div class="highlight"><pre><span></span><span class="c1"># models.py</span>
<span class="kn">import</span> <span class="nn">peewee</span>
<span class="c1"># Criamos o banco de dados</span>
<span class="n">db</span> <span class="o">=</span> <span class="n">peewee</span><span class="o">.</span><span class="n">SqliteDatabase</span><span class="p">(</span><span class="s1">'codigo_avulso.db'</span><span class="p">)</span>
<span class="k">class</span> <span class="nc">BaseModel</span><span class="p">(</span><span class="n">peewee</span><span class="o">.</span><span class="n">Model</span><span class="p">):</span>
<span class="sd">"""Classe model base"""</span>
<span class="k">class</span> <span class="nc">Meta</span><span class="p">:</span>
<span class="c1"># Indica em qual banco de dados a tabela</span>
<span class="c1"># 'author' sera criada (obrigatorio). Neste caso,</span>
<span class="c1"># utilizamos o banco 'codigo_avulso.db' criado anteriormente</span>
<span class="n">database</span> <span class="o">=</span> <span class="n">db</span>
</pre></div>
<p>A class <code>BaseModel</code> é responsável por criar a conexão com nosso banco de dados.</p>
<p>Agora, vamos criar a model que representa os autores:</p>
<div class="highlight"><pre><span></span><span class="c1"># models.py</span>
<span class="k">class</span> <span class="nc">Author</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Classe que representa a tabela Author</span>
<span class="sd"> """</span>
<span class="c1"># A tabela possui apenas o campo 'name', que receberá o nome do autor sera unico</span>
<span class="n">name</span> <span class="o">=</span> <span class="n">peewee</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">unique</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
</pre></div>
<p>Se observamos a model <code>Author</code>, veremos que não foi especificado nenhuma coluna como <em>primary key</em> (chave primaria), sendo assim o Peewee irá criar um campo chamado <code>id</code> do tipo inteiro com auto incremento para funcionar como chave primária.
Em seguida, no mesmo arquivo <code>models.py</code> criamos a classe que representa os livros. Ela possui uma relação de "muitos para um" com a tabela de autores, ou seja, cada livro possui apenas um autor, mas um autor pode possuir vários livros.</p>
<div class="highlight"><pre><span></span><span class="c1"># models.py</span>
<span class="k">class</span> <span class="nc">Book</span><span class="p">(</span><span class="n">BaseModel</span><span class="p">):</span>
<span class="sd">"""</span>
<span class="sd"> Classe que representa a tabela Book</span>
<span class="sd"> """</span>
<span class="c1"># A tabela possui apenas o campo 'title', que receberá o nome do livro</span>
<span class="n">title</span> <span class="o">=</span> <span class="n">peewee</span><span class="o">.</span><span class="n">CharField</span><span class="p">(</span><span class="n">unique</span><span class="o">=</span><span class="kc">True</span><span class="p">)</span>
<span class="c1"># Chave estrangeira para a tabela Author</span>
<span class="n">author</span> <span class="o">=</span> <span class="n">peewee</span><span class="o">.</span><span class="n">ForeignKeyField</span><span class="p">(</span><span class="n">Author</span><span class="p">)</span>
</pre></div>
<p>Agora, adicionamos o código que cria as tabelas <code>Author</code> e <code>Book</code>.</p>
<div class="highlight"><pre><span></span><span class="c1"># models.py</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="k">try</span><span class="p">:</span>
<span class="n">Author</span><span class="o">.</span><span class="n">create_table</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Tabela 'Author' criada com sucesso!"</span><span class="p">)</span>
<span class="k">except</span> <span class="n">peewee</span><span class="o">.</span><span class="n">OperationalError</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Tabela 'Author' ja existe!"</span><span class="p">)</span>
<span class="k">try</span><span class="p">:</span>
<span class="n">Book</span><span class="o">.</span><span class="n">create_table</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Tabela 'Book' criada com sucesso!"</span><span class="p">)</span>
<span class="k">except</span> <span class="n">peewee</span><span class="o">.</span><span class="n">OperationalError</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="s2">"Tabela 'Book' ja existe!"</span><span class="p">)</span>
</pre></div>
<p>excerpt
Agora executamos o <code>models.py</code>:</p>
<div class="highlight"><pre><span></span><span class="err">python models.py</span>
</pre></div>
<p>A estrutura do diretório ficou assim:</p>
<div class="highlight"><pre><span></span>.
├── codigo_avulso.db
├── models.py
</pre></div>
<p>Após executarmos o código, será criado um arquivo de nome <code>codigo_avulso.db</code> no mesmo diretório do nosso arquivo <code>models.py</code>, contendo as tabelas <code>Author</code> e <code>Book</code>.</p>
<h2>Realizando o CRUD</h2>
<p>Agora vamos seguir com as 4 principais operações que podemos realizar em um banco de dados, também conhecida como CRUD.</p>
<p>A sigla <code>CRUD</code> é comumente utilizada para designar as quatro operações básicas que pode-se executar em um banco de dados, sendo elas: </p>
<div class="highlight"><pre><span></span><span class="err">- Create (criar um novo registro no banco)</span>
<span class="err">- Read (ler/consultar um registro)</span>
<span class="err">- Update (atualizar um registro)</span>
<span class="err">- Delete (excluir um registro do banco)</span>
</pre></div>
<p>Iremos abordar cada uma dessas operações.</p>
<h3>Create: Inserindo dados no banco</h3>
<p>Agora, vamos popular nosso banco com alguns autores e seus respectivos livros. Para isso criamos um arquivo <code>create.py</code>. A estrutura do diretório ficou assim:</p>
<div class="highlight"><pre><span></span>.
├── codigo_avulso.db
├── models.py
├── create.py
</pre></div>
<p>A criação dos registros no banco pode ser feito através do método <code>create</code>, quando desejamos inserir um registro apenas; ou pelo método <code>insert_many</code>, quando desejamos inserir vários registros de uma vez em uma mesma tabela.</p>
<div class="highlight"><pre><span></span><span class="c1"># create.py</span>
<span class="kn">from</span> <span class="nn">models</span> <span class="kn">import</span> <span class="n">Author</span><span class="p">,</span> <span class="n">Book</span>
<span class="c1"># Inserimos um autor de nome "H. G. Wells" na tabela 'Author'</span>
<span class="n">author_1</span> <span class="o">=</span> <span class="n">Author</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'H. G. Wells'</span><span class="p">)</span>
<span class="c1"># Inserimos um autor de nome "Julio Verne" na tabela 'Author'</span>
<span class="n">author_2</span> <span class="o">=</span> <span class="n">Author</span><span class="o">.</span><span class="n">create</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s1">'Julio Verne'</span><span class="p">)</span>
<span class="n">book_1</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'title'</span><span class="p">:</span> <span class="s1">'A Máquina do Tempo'</span><span class="p">,</span>
<span class="s1">'author_id'</span><span class="p">:</span> <span class="n">author_1</span><span class="p">,</span>
<span class="p">}</span>
<span class="n">book_2</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'title'</span><span class="p">:</span> <span class="s1">'Guerra dos Mundos'</span><span class="p">,</span>
<span class="s1">'author_id'</span><span class="p">:</span> <span class="n">author_1</span><span class="p">,</span>
<span class="p">}</span>
<span class="n">book_3</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'title'</span><span class="p">:</span> <span class="s1">'Volta ao Mundo em 80 Dias'</span><span class="p">,</span>
<span class="s1">'author_id'</span><span class="p">:</span> <span class="n">author_2</span><span class="p">,</span>
<span class="p">}</span>
<span class="n">book_4</span> <span class="o">=</span> <span class="p">{</span>
<span class="s1">'title'</span><span class="p">:</span> <span class="s1">'Vinte Mil Leguas Submarinas'</span><span class="p">,</span>
<span class="s1">'author_id'</span><span class="p">:</span> <span class="n">author_1</span><span class="p">,</span>
<span class="p">}</span>
<span class="n">books</span> <span class="o">=</span> <span class="p">[</span><span class="n">book_1</span><span class="p">,</span> <span class="n">book_2</span><span class="p">,</span> <span class="n">book_3</span><span class="p">,</span> <span class="n">book_4</span><span class="p">]</span>
<span class="c1"># Inserimos os quatro livros na tabela 'Book'</span>
<span class="n">Book</span><span class="o">.</span><span class="n">insert_many</span><span class="p">(</span><span class="n">books</span><span class="p">)</span><span class="o">.</span><span class="n">execute</span><span class="p">()</span>
</pre></div>
<h3>Read: Consultando dados no banco</h3>
<p>O Peewee possui comandos destinados a realizar consultas no banco. De maneira semelhante ao conhecido <code>SELECT</code>. Podemos fazer essa consulta de duas maneiras. Se desejamos o primeiro registro que corresponda a nossa pesquisa, podemos utilizar o método <code>get()</code>.</p>
<div class="highlight"><pre><span></span><span class="c1"># read.py</span>
<span class="kn">from</span> <span class="nn">models</span> <span class="kn">import</span> <span class="n">Author</span><span class="p">,</span> <span class="n">Book</span>
<span class="n">book</span> <span class="o">=</span> <span class="n">Book</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">Book</span><span class="o">.</span><span class="n">title</span> <span class="o">==</span> <span class="s2">"Volta ao Mundo em 80 Dias"</span><span class="p">)</span><span class="o">.</span><span class="n">get</span><span class="p">()</span>
<span class="nb">print</span><span class="p">(</span><span class="n">book</span><span class="o">.</span><span class="n">title</span><span class="p">)</span>
<span class="c1"># Resultado</span>
<span class="c1"># * Volta ao Munto em 80 Dias</span>
</pre></div>
<p>Porém, se desejamos mais de um registro, utilizamos o método <code>select</code>. Por exemplo, para consultar todos os livros escritos pelo autor "H. G. Wells".</p>
<div class="highlight"><pre><span></span><span class="c1"># read.py</span>
<span class="n">books</span> <span class="o">=</span> <span class="n">Book</span><span class="o">.</span><span class="n">select</span><span class="p">()</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">Author</span><span class="p">)</span><span class="o">.</span><span class="n">where</span><span class="p">(</span><span class="n">Author</span><span class="o">.</span><span class="n">name</span><span class="o">==</span><span class="s1">'H. G. Wells'</span><span class="p">)</span>
<span class="c1"># Exibe a quantidade de registros que corresponde a nossa pesquisa</span>
<span class="nb">print</span><span class="p">(</span><span class="n">books</span><span class="o">.</span><span class="n">count</span><span class="p">())</span>
<span class="k">for</span> <span class="n">book</span> <span class="ow">in</span> <span class="n">books</span><span class="p">:</span>
<span class="nb">print</span><span class="p">(</span><span class="n">book</span><span class="o">.</span><span class="n">title</span><span class="p">)</span>
<span class="c1"># Resultado:</span>
<span class="c1"># * A Máquina do Tempo</span>
<span class="c1"># * Guerra dos Mundos</span>
<span class="c1"># * Vinte Mil Leguas Submarinas</span>
</pre></div>
<p>Também podemos utilizar outras comandos do SQL como <code>limit</code> e <code>group</code> (para mais detalhes, ver a documentação <a href="https://peewee.readthedocs.io/en/latest/index.html">aqui</a>).</p>
<p>A estrutura do diretório ficou assim:</p>
<div class="highlight"><pre><span></span>.
├── codigo_avulso.db
├── models.py
├── create.py
├── read.py
</pre></div>
<h3>Update: Alterando dados no banco</h3>
<p>Alterar dados também é bem simples. No exemplo anterior, se observarmos o resultado da consulta dos livros do autor "H. G. Wells", iremos nos deparar com o livro de título "Vinte Mil Léguas Submarinas". Se você, caro leitor, gosta de contos de ficção-científica, sabe que esta obra foi escrito por "Julio Verne", coincidentemente um dos autores que também estão cadastrados em nosso banco. Sendo assim, vamos corrigir o autor do respectivo livro.</p>
<p>Primeiro vamos buscar o registro do autor e do livro:</p>
<div class="highlight"><pre><span></span><span class="c1"># update.py</span>
<span class="kn">from</span> <span class="nn">models</span> <span class="kn">import</span> <span class="n">Author</span><span class="p">,</span> <span class="n">Book</span>
<span class="n">new_author</span> <span class="o">=</span> <span class="n">Author</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">Author</span><span class="o">.</span><span class="n">name</span> <span class="o">==</span> <span class="s1">'Julio Verne'</span><span class="p">)</span>
<span class="n">book</span> <span class="o">=</span> <span class="n">Book</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">Book</span><span class="o">.</span><span class="n">title</span><span class="o">==</span><span class="s2">"Vinte Mil Leguas Submarinas"</span><span class="p">)</span>
</pre></div>
<p>Agora vamos alterar o autor e gravar essa alteração no banco.</p>
<div class="highlight"><pre><span></span><span class="c1"># update.py</span>
<span class="c1"># Alteramos o autor do livro</span>
<span class="n">book</span><span class="o">.</span><span class="n">author</span> <span class="o">=</span> <span class="n">new_author</span>
<span class="c1"># Salvamos a alteração no banco</span>
<span class="n">book</span><span class="o">.</span><span class="n">save</span><span class="p">()</span>
</pre></div>
<p>A estrutura do diretório ficou assim:</p>
<div class="highlight"><pre><span></span>.
├── codigo_avulso.db
├── models.py
├── create.py
├── read.py
├── update.py
</pre></div>
<h3>Delete: Deletando dados do banco</h3>
<p>Assim como as operações anteriores, também podemos deletar registros do banco de maneira bem prática. Como exemplo, vamos deletar o livro "Guerra dos Mundos" do nosso banco de dados.</p>
<div class="highlight"><pre><span></span><span class="c1"># delete.py</span>
<span class="kn">from</span> <span class="nn">models</span> <span class="kn">import</span> <span class="n">Author</span><span class="p">,</span> <span class="n">Book</span>
<span class="c1"># Buscamos o livro que desejamos excluir do banco</span>
<span class="n">book</span> <span class="o">=</span> <span class="n">Book</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">Book</span><span class="o">.</span><span class="n">title</span><span class="o">==</span><span class="s2">"Guerra dos Mundos"</span><span class="p">)</span>
<span class="c1"># Excluimos o livro do banco</span>
<span class="n">book</span><span class="o">.</span><span class="n">delete_instance</span><span class="p">()</span>
</pre></div>
<p>Simples não?</p>
<p>A estrutura do diretório ficou assim:</p>
<div class="highlight"><pre><span></span>.
├── codigo_avulso.db
├── models.py
├── create.py
├── read.py
├── update.py
├── delete.py
</pre></div>
<h2>Conclusão</h2>
<p>É isso pessoal. Este tutorial foi uma introdução bem enxuta sobre o Peewee. Ainda existem muitos tópicos que não abordei aqui, como a criação de <em>primary_key</em>, de campos <em>many2many</em> entre outros recursos, pois foge do escopo deste tutorial. Se você gostou do ORM, aconselho a dar uma olhada também na sua documentação, para conseguir extrair todo o potencial da ferramenta. A utilização de um ORM evita que o desenvolvedor perca tempo escrevendo <em>query</em> SQL e foque totalmente no desenvolvimento de código.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://peewee.readthedocs.io/en/latest/index.html">Documentação do Peewee (em inglês)</a></li>
<li><a href="https://www.blog.pythonlibrary.org/2014/07/17/an-intro-to-peewee-another-python-orm/">An Intro to peewee – Another Python ORM</a></li>
<li><a href="https://jonathansoma.com/tutorials/webapps/intro-to-peewee/">Introduction to peewee</a></li>
<li><a href="https://www.novatec.com.br/livros/introducao-sql/">Introdução à Linguagem SQL</a></li>
</ul>
2017-07-21T02:45:24+00:00
Michell Stuttgart
-
Flávio Coelho: Curso de introdução a criptomoedas - Aula 01
https://pyinsci.blogspot.com/2017/06/curso-de-introducao-criptomoedas-aula-01.html
For the Portuguese speaking readers of this blog, I am starting an Introductory course on Cryptocurrencies and applications on the blockchain which is an online version of a standard classroom course I am starting now at FGV.
This is the first lecture which is basically an intro to the topic and the structure of the course.
The online version should have the main content of the lectures on
2017-06-17T13:50:44+00:00
Anonymous