mirror of
https://github.com/balkian/balkian.github.com.git
synced 2025-02-23 10:15:05 +00:00
92 lines
24 KiB
HTML
92 lines
24 KiB
HTML
<!doctype html><html lang=en-us dir=ltr><head><meta charset=utf-8><meta name=viewport content='width=device-width,initial-scale=1'><meta name=description content="Long story short: I’m now using uv, and so should you. It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.\n"><title>uv - One rust tool to rule all pythons</title>
|
|
<link rel=canonical href=https://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/><link rel=stylesheet href=/scss/style.min.663803bebe609202d5b39d848f2d7c2dc8b598a2d879efa079fa88893d29c49c.css><meta property='og:title' content="uv - One rust tool to rule all pythons"><meta property='og:description' content="Long story short: I’m now using uv, and so should you. It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.\n"><meta property='og:url' content='https://balkian.com/p/uv-one-rust-tool-to-rule-all-pythons/'><meta property='og:site_name' content='J. Fernando Sánchez'><meta property='og:type' content='article'><meta property='article:section' content='Post'><meta property='article:tag' content='python'><meta property='article:published_time' content='2025-02-17T23:02:47+01:00'><meta property='article:modified_time' content='2025-02-17T23:02:47+01:00'><meta property='og:image' content='https://balkian.com/img/uv.png'><meta name=twitter:title content="uv - One rust tool to rule all pythons"><meta name=twitter:description content="Long story short: I’m now using uv, and so should you. It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.\n"><meta name=twitter:card content="summary_large_image"><meta name=twitter:image content='https://balkian.com/img/uv.png'><link rel="shortcut icon" href=/img/favicon.ico></head><body class=article-page><script>(function(){const e="StackColorScheme";localStorage.getItem(e)||localStorage.setItem(e,"auto")})()</script><script>(function(){const t="StackColorScheme",e=localStorage.getItem(t),n=window.matchMedia("(prefers-color-scheme: dark)").matches===!0;e=="dark"||e==="auto"&&n?document.documentElement.dataset.scheme="dark":document.documentElement.dataset.scheme="light"})()</script><div class="container main-container flex on-phone--column extended"><aside class="sidebar left-sidebar sticky"><button class="hamburger hamburger--spin" type=button id=toggle-menu aria-label="Toggle Menu">
|
|
<span class=hamburger-box><span class=hamburger-inner></span></span></button><header><figure class=site-avatar><a href=/><img src=/img/me_hu_caa0902d7ae4598e.png width=300 height=300 class=site-logo loading=lazy alt=Avatar>
|
|
</a><span class=emoji>💭</span></figure><div class=site-meta><h1 class=site-name><a href=/>J. Fernando Sánchez</a></h1><h2 class=site-description>My ramblings and reflections</h2></div></header><ol class=menu-social><li><a href=https://github.com/CaiJimmy/hugo-theme-stack target=_blank title=GitHub rel=me><svg class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 19c-4.3 1.4-4.3-2.5-6-3m12 5v-3.5c0-1 .1-1.4-.5-2 2.8-.3 5.5-1.4 5.5-6a4.6 4.6.0 00-1.3-3.2 4.2 4.2.0 00-.1-3.2s-1.1-.3-3.5 1.3a12.3 12.3.0 00-6.2.0C6.5 2.8 5.4 3.1 5.4 3.1a4.2 4.2.0 00-.1 3.2A4.6 4.6.0 004 9.5c0 4.6 2.7 5.7 5.5 6-.6.6-.6 1.2-.5 2V21"/></svg></a></li><li><a href=https://git.sinpapel.es/balkian target=_blank title=gitea rel=me><svg viewBox="0 0 640 640" width="32" height="32"><path d="m395.9 484.2-126.9-61c-12.5-6-17.9-21.2-11.8-33.8l61-126.9c6-12.5 21.2-17.9 33.8-11.8 17.2 8.3 27.1 13 27.1 13l-.1-109.2 16.7-.1.1 117.1s57.4 24.2 83.1 40.1c3.7 2.3 10.2 6.8 12.9 14.4 2.1 6.1 2 13.1-1 19.3l-61 126.9c-6.2 12.7-21.4 18.1-33.9 12" style="fill:#fff"/><path d="M622.7 149.8c-4.1-4.1-9.6-4-9.6-4s-117.2 6.6-177.9 8c-13.3.3-26.5.6-39.6.7v117.2c-5.5-2.6-11.1-5.3-16.6-7.9.0-36.4-.1-109.2-.1-109.2-29 .4-89.2-2.2-89.2-2.2s-141.4-7.1-156.8-8.5c-9.8-.6-22.5-2.1-39 1.5-8.7 1.8-33.5 7.4-53.8 26.9C-4.9 212.4 6.6 276.2 8 285.8c1.7 11.7 6.9 44.2 31.7 72.5 45.8 56.1 144.4 54.8 144.4 54.8s12.1 28.9 30.6 55.5c25 33.1 50.7 58.9 75.7 62 63 0 188.9-.1 188.9-.1s12 .1 28.3-10.3c14-8.5 26.5-23.4 26.5-23.4S547 483 565 451.5c5.5-9.7 10.1-19.1 14.1-28 0 0 55.2-117.1 55.2-231.1-1.1-34.5-9.6-40.6-11.6-42.6M125.6 353.9c-25.9-8.5-36.9-18.7-36.9-18.7S69.6 321.8 60 295.4c-16.5-44.2-1.4-71.2-1.4-71.2s8.4-22.5 38.5-30c13.8-3.7 31-3.1 31-3.1s7.1 59.4 15.7 94.2c7.2 29.2 24.8 77.7 24.8 77.7s-26.1-3.1-43-9.1m300.3 107.6s-6.1 14.5-19.6 15.4c-5.8.4-10.3-1.2-10.3-1.2s-.3-.1-5.3-2.1l-112.9-55s-10.9-5.7-12.8-15.6c-2.2-8.1 2.7-18.1 2.7-18.1L322 273s4.8-9.7 12.2-13c.6-.3 2.3-1 4.5-1.5 8.1-2.1 18 2.8 18 2.8L467.4 315s12.6 5.7 15.3 16.2c1.9 7.4-.5 14-1.8 17.2-6.3 15.4-55 113.1-55 113.1" style="fill:#609926"/><path d="M326.8 380.1c-8.2.1-15.4 5.8-17.3 13.8s2 16.3 9.1 20c7.7 4 17.5 1.8 22.7-5.4 5.1-7.1 4.3-16.9-1.8-23.1l24-49.1c1.5.1 3.7.2 6.2-.5 4.1-.9 7.1-3.6 7.1-3.6 4.2 1.8 8.6 3.8 13.2 6.1 4.8 2.4 9.3 4.9 13.4 7.3.9.5 1.8 1.1 2.8 1.9 1.6 1.3 3.4 3.1 4.7 5.5 1.9 5.5-1.9 14.9-1.9 14.9-2.3 7.6-18.4 40.6-18.4 40.6-8.1-.2-15.3 5-17.7 12.5-2.6 8.1 1.1 17.3 8.9 21.3s17.4 1.7 22.5-5.3c5-6.8 4.6-16.3-1.1-22.6 1.9-3.7 3.7-7.4 5.6-11.3 5-10.4 13.5-30.4 13.5-30.4.9-1.7 5.7-10.3 2.7-21.3-2.5-11.4-12.6-16.7-12.6-16.7-12.2-7.9-29.2-15.2-29.2-15.2s0-4.1-1.1-7.1c-1.1-3.1-2.8-5.1-3.9-6.3 4.7-9.7 9.4-19.3 14.1-29-4.1-2-8.1-4-12.2-6.1-4.8 9.8-9.7 19.7-14.5 29.5-6.7-.1-12.9 3.5-16.1 9.4-3.4 6.3-2.7 14.1 1.9 19.8z" style="fill:#609926"/></svg></a></li><li><a href='https://scholar.google.com/citations?user=JLNusZ8AAAAJ&hl=en' target=_blank title="Google scholar" rel=me><svg aria-label="Google Scholar" role="img" viewBox="0 0 512 512"><rect width="512" height="512" rx="15%" fill="#4285f4"/><path fill="#fff" d="M213 111l-107 94h69c5 45 41 64 78 67-7 18-4 27 7 39-43 1-103 26-103 67 4 45 63 54 92 54 38 1 81-19 90-54 4-35-10-54-31-71-23-18-28-28-21-40 15-17 35-27 39-51 2-17-2-28-6-43l45-38-1 16c-3 2-5 6-5 9v103c2 13 22 11 23 0V160c0-3-2-7-5-8v-25l16-16zm58 141c-61 10-87-87-38-99 56-11 83 86 38 99zm-5 73c60 13 61 63 10 78-44 9-82-4-81-30 0-25 35-48 71-48z"/></svg></a></li></ol><ol class=menu id=main-menu><li><a href=/><svg class="icon icon-tabler icon-tabler-home" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><polyline points="5 12 3 12 12 3 21 12 19 12"/><path d="M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7"/><path d="M9 21v-6a2 2 0 012-2h2a2 2 0 012 2v6"/></svg>
|
|
<span>Home</span></a></li><li><a href=/projects/><svg class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 15"/></svg>
|
|
<span>Projects</span></a></li><li><a href=/archives/><svg class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><rect x="3" y="4" width="18" height="4" rx="2"/><path d="M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8"/><line x1="10" y1="12" x2="14" y2="12"/></svg>
|
|
<span>Archives</span></a></li><li><a href=/search/><svg class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="10" cy="10" r="7"/><line x1="21" y1="21" x2="15" y2="15"/></svg>
|
|
<span>Search</span></a></li><li><a href=/page/cheatsheet/><svg class="icon icon-tabler icon-tabler-infinity" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M9.828 9.172a4 4 0 100 5.656A10 10 0 0012 12a10 10 0 012.172-2.828 4 4 0 110 5.656A10 10 0 0112 12 10 10 0 009.828 9.172"/></svg>
|
|
<span>Cheatsheets</span></a></li><li><a href=/links/><svg class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M10 14a3.5 3.5.0 005 0l4-4a3.5 3.5.0 00-5-5l-.5.5"/><path d="M14 10a3.5 3.5.0 00-5 0l-4 4a3.5 3.5.0 005 5l.5-.5"/></svg>
|
|
<span>Links</span></a></li><li class=menu-bottom-section><ol class=menu><li id=dark-mode-toggle><svg class="icon icon-tabler icon-tabler-toggle-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="8" cy="12" r="2"/><rect x="2" y="6" width="20" height="12" rx="6"/></svg>
|
|
<svg class="icon icon-tabler icon-tabler-toggle-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="16" cy="12" r="2"/><rect x="2" y="6" width="20" height="12" rx="6"/></svg>
|
|
<span>Dark Mode</span></li></ol></li></ol></aside><aside class="sidebar right-sidebar sticky"><section class="widget archives"><div class=widget-icon><svg class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><line x1="5" y1="9" x2="19" y2="9"/><line x1="5" y1="15" x2="19" y2="15"/><line x1="11" y1="4" x2="7" y2="20"/><line x1="17" y1="4" x2="13" y2="20"/></svg></div><h2 class="widget-title section-title">Table of contents</h2><div class=widget--toc><nav id=TableOfContents><ol><li><a href=#context>Context</a></li><li><a href=#alternatives-poetry>Alternatives (poetry)</a></li><li><a href=#enter-light-uv>Enter <del>light</del> <code>uv</code></a></li><li><a href=#common-operations>Common operations</a><ol><li><a href=#initialize-a-repository>Initialize a repository</a></li><li><a href=#adding-dependencies>Adding dependencies</a></li><li><a href=#running-commands-inside-the-environment>Running commands inside the environment</a></li><li><a href=#dependency-tree>Dependency tree</a></li></ol></li></ol></nav></div></section></aside><main class="main full-width"><article class="has-image main-article"><header class=article-header><div class=article-image><a href=/p/uv-one-rust-tool-to-rule-all-pythons/><img src=/img/uv.png loading=lazy alt="Featured image of post uv - One rust tool to rule all pythons"></a></div><div class=article-details><header class=article-category><a href=/categories/programming/ style=background-color:gold;color:#000>Programming</a></header><div class=article-title-wrapper><h2 class=article-title><a href=/p/uv-one-rust-tool-to-rule-all-pythons/>uv - One rust tool to rule all pythons</a></h2></div><footer class=article-time><div><svg class="icon icon-tabler icon-tabler-calendar-time" width="56" height="56" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><path d="M11.795 21H5a2 2 0 01-2-2V7a2 2 0 012-2h12a2 2 0 012 2v4"/><circle cx="18" cy="18" r="4"/><path d="M15 3v4"/><path d="M7 3v4"/><path d="M3 11h16"/><path d="M18 16.496V18l1 1"/></svg>
|
|
<time class=article-time--published>17 Feb 2025</time></div><div><svg class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="12" r="9"/><polyline points="12 7 12 12 15 15"/></svg>
|
|
<time class=article-time--reading>4 minute read</time></div></footer></div></header><section class=article-content><p>Long story short: I’m now using <a class=link href=https://github.com/astral-sh/uv target=_blank rel=noopener>uv</a>, and so should you.
|
|
It is a great replacement for pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.</p><h2 id=context>Context</h2><p>For years, my strategy to manage python projects has been a mix of a custom <code>setup.py</code>, several hand-crafted <code>requirements.txt</code> files (through <code>pip freeze</code>), a custom virtualenv per project, and multiple tools to upload to PyPI.
|
|
Although this works, this setup has many drawbacks:</p><ul><li>It requires user intervention (creating a venv, sourcing it, handling new deps). This isn’t ideal if you want new (probably inexperienced) users to use your projects.</li><li>On a similar note, the whole process needs to be well documented if you want other users to contribute or maintain the code.</li><li>Pinning dependency versions is finicky, and I’ve run into problems beause of that.</li><li>Creating a new project involves a template, or copying files from an older project.</li></ul><p>Of course, this is nothing new.
|
|
There is a whole site dedicated to <a class=link href=https://packaging.python.org/en/latest/ target=_blank rel=noopener>packaging your Python project</a>.
|
|
A plethora of different projects have come and go, with varying degrees of success.</p><h2 id=alternatives-poetry>Alternatives (poetry)</h2><p>About a year before trying <code>uv</code>, I tried to catch up with the ecosystem and get to know the <code>blessed new way</code>.
|
|
However, the task proved to be a little more difficult, as the landscape is filled with a myriad of alternatives, each with their own set of drawbacks and detractors.
|
|
Packaging has historically been a weak spot, in ironical contradiction to the Zen of Python’s “There should be one– and preferably only one –obvious way to do it”,</p><p>I eventually settled on <a class=link href=https://python-poetry.org/ target=_blank rel=noopener>poetry</a>.
|
|
Mostly because it seemed like the most popular alternative.</p><p>There are many things I liked about it.
|
|
First of all, having a convention for dependencies (<code>pyproject.toml</code>) and a tool that properly handles them was nice.
|
|
It also removed the need to remember specific incantations to build and publish my Python projects.
|
|
Lastly, I mixed it <code>poetry2nix</code> to create reproducible python environments using nix.
|
|
This makes for a very powerful experience.</p><p>However, there were multiple hiccups.
|
|
First of all, it took me some time to figure out which specific fields to use (each tool can define ad-hoc properties in a the <code>pyproject.toml</code> file), and some of them seemed redundant with the more generic ones.
|
|
Full disclosure, this specific point might be a mistake on my side, and I do not remember the details.
|
|
The second one is speed.
|
|
(Re-)creating an environment took a non-negligible amount of time.</p><h2 id=enter-light-uv>Enter <del>light</del> <code>uv</code></h2><p>According to its repository, <code>uv </code>can replace pip, pip-tools, pipx, poetry, pyenv, twine, virtualenv, and more.
|
|
Not only that, but it also claims to do that 10-100 times faster than pip.
|
|
I must admit that it being written in rust was a another selling point for me, as I’m looking for excuses to collaborate in a decently-sized rust projejct.</p><p>Installing it is dead simple: simply download the binary (e.g., with curl) or run <code>pip install uv</code>.
|
|
You won’t need much more: <code>uv</code> seems to just do the right thing out of the box.
|
|
And it does it really, really fast.
|
|
The rest of the time it gets out of the way.</p><p>My only gripe so far is that I don’t seem to find a built-in command to drop into a shell, but that is nothing that <code>uv run $SHELL</code> cannot fix.</p><h2 id=common-operations>Common operations</h2><h3 id=initialize-a-repository>Initialize a repository</h3><div class=highlight><div class=chroma><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
|
|
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-fallback data-lang=fallback><span class=line><span class=cl>uv init
|
|
</span></span></code></pre></td></tr></table></div></div><h3 id=adding-dependencies>Adding dependencies</h3><div class=highlight><div class=chroma><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
|
|
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-fallback data-lang=fallback><span class=line><span class=cl>uv add senpy
|
|
</span></span></code></pre></td></tr></table></div></div><h3 id=running-commands-inside-the-environment>Running commands inside the environment</h3><div class=highlight><div class=chroma><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt>1
|
|
</span><span class=lnt>2
|
|
</span><span class=lnt>3
|
|
</span><span class=lnt>4
|
|
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-fallback data-lang=fallback><span class=line><span class=cl>uv run <COMMAND>
|
|
</span></span><span class=line><span class=cl>
|
|
</span></span><span class=line><span class=cl># e.g., run a shell using your python version and dependencies
|
|
</span></span><span class=line><span class=cl>uv run $SHELL
|
|
</span></span></code></pre></td></tr></table></div></div><h3 id=dependency-tree>Dependency tree</h3><div class=highlight><div class=chroma><table class=lntable><tr><td class=lntd><pre tabindex=0 class=chroma><code><span class=lnt> 1
|
|
</span><span class=lnt> 2
|
|
</span><span class=lnt> 3
|
|
</span><span class=lnt> 4
|
|
</span><span class=lnt> 5
|
|
</span><span class=lnt> 6
|
|
</span><span class=lnt> 7
|
|
</span><span class=lnt> 8
|
|
</span><span class=lnt> 9
|
|
</span><span class=lnt>10
|
|
</span><span class=lnt>11
|
|
</span><span class=lnt>12
|
|
</span><span class=lnt>13
|
|
</span><span class=lnt>14
|
|
</span><span class=lnt>15
|
|
</span><span class=lnt>16
|
|
</span><span class=lnt>17
|
|
</span><span class=lnt>18
|
|
</span><span class=lnt>19
|
|
</span></code></pre></td><td class=lntd><pre tabindex=0 class=chroma><code class=language-fallback data-lang=fallback><span class=line><span class=cl>uv shell
|
|
</span></span><span class=line><span class=cl>Resolved 44 packages in 1ms
|
|
</span></span><span class=line><span class=cl>my-project v0.1.0
|
|
</span></span><span class=line><span class=cl>├── fastapi[standard] v0.115.8
|
|
</span></span><span class=line><span class=cl>│ ├── pydantic v2.10.6
|
|
</span></span><span class=line><span class=cl>│ │ ├── annotated-types v0.7.0
|
|
</span></span><span class=line><span class=cl>│ │ ├── pydantic-core v2.27.2
|
|
</span></span><span class=line><span class=cl>│ │ │ └── typing-extensions v4.12.2
|
|
</span></span><span class=line><span class=cl>│ │ └── typing-extensions v4.12.2
|
|
</span></span><span class=line><span class=cl>│ ├── starlette v0.45.3
|
|
</span></span><span class=line><span class=cl>│ │ └── anyio v4.8.0
|
|
</span></span><span class=line><span class=cl>│ │ ├── exceptiongroup v1.2.2
|
|
</span></span><span class=line><span class=cl>│ │ ├── idna v3.10
|
|
</span></span><span class=line><span class=cl>│ │ ├── sniffio v1.3.1
|
|
</span></span><span class=line><span class=cl>│ │ └── typing-extensions v4.12.2
|
|
</span></span><span class=line><span class=cl>│ ├── typing-extensions v4.12.2
|
|
</span></span><span class=line><span class=cl>│ ├── email-validator v2.2.0 (extra: standard)
|
|
</span></span><span class=line><span class=cl>│ │ ├── dnspython v2.7.0
|
|
</span></span><span class=line><span class=cl>...
|
|
</span></span></code></pre></td></tr></table></div></div></section><footer class=article-footer><section class=article-tags><a href=/tags/python/>Python</a></section><section class=article-copyright><svg class="icon icon-tabler icon-tabler-copyright" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentcolor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z"/><circle cx="12" cy="12" r="9"/><path d="M14.5 9a3.5 4 0 100 6"/></svg>
|
|
<span>Licensed under CC BY-NC-SA 4.0</span></section></footer><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css integrity=sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV crossorigin=anonymous><script src=https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js integrity=sha384-XjKyOOlGwcjNTAIQHIpgOno0Hl1YQqzUOEleOLALmuqehneUG+vnGctmUb0ZY0l8 crossorigin=anonymous defer></script><script src=https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js integrity=sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05 crossorigin=anonymous defer></script><script>window.addEventListener("DOMContentLoaded",()=>{const e=document.querySelector(".main-article");renderMathInElement(e,{delimiters:[{left:"$$",right:"$$",display:!0},{left:"$",right:"$",display:!1},{left:"\\(",right:"\\)",display:!1},{left:"\\[",right:"\\]",display:!0}],ignoredClasses:["gist"]})})</script></article><footer class=site-footer><section class=copyright>©
|
|
2012 -
|
|
2025 J. Fernando Sánchez</section><section class=powerby>Built with <a href=https://gohugo.io/ target=_blank rel=noopener>Hugo</a><br>Theme <b><a href=https://github.com/CaiJimmy/hugo-theme-stack target=_blank rel=noopener data-version=3.30.0>Stack</a></b> designed by <a href=https://jimmycai.com target=_blank rel=noopener>Jimmy</a></section></footer><div class=pswp tabindex=-1 role=dialog aria-hidden=true><div class=pswp__bg></div><div class=pswp__scroll-wrap><div class=pswp__container><div class=pswp__item></div><div class=pswp__item></div><div class=pswp__item></div></div><div class="pswp__ui pswp__ui--hidden"><div class=pswp__top-bar><div class=pswp__counter></div><button class="pswp__button pswp__button--close" title="Close (Esc)"></button>
|
|
<button class="pswp__button pswp__button--share" title=Share></button>
|
|
<button class="pswp__button pswp__button--fs" title="Toggle fullscreen"></button>
|
|
<button class="pswp__button pswp__button--zoom" title="Zoom in/out"></button><div class=pswp__preloader><div class=pswp__preloader__icn><div class=pswp__preloader__cut><div class=pswp__preloader__donut></div></div></div></div></div><div class="pswp__share-modal pswp__share-modal--hidden pswp__single-tap"><div class=pswp__share-tooltip></div></div><button class="pswp__button pswp__button--arrow--left" title="Previous (arrow left)">
|
|
</button>
|
|
<button class="pswp__button pswp__button--arrow--right" title="Next (arrow right)"></button><div class=pswp__caption><div class=pswp__caption__center></div></div></div></div></div><script src=https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js integrity="sha256-ePwmChbbvXbsO02lbM3HoHbSHTHFAeChekF1xKJdleo=" crossorigin=anonymous defer></script><script src=https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js integrity="sha256-UKkzOn/w1mBxRmLLGrSeyB4e1xbrp4xylgAWb3M42pU=" crossorigin=anonymous defer></script><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.min.css crossorigin=anonymous><link rel=stylesheet href=https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.css crossorigin=anonymous></main></div><script src=https://cdn.jsdelivr.net/npm/node-vibrant@3.1.6/dist/vibrant.min.js integrity="sha256-awcR2jno4kI5X0zL8ex0vi2z+KMkF24hUW8WePSA9HM=" crossorigin=anonymous></script><script type=text/javascript src=/ts/main.1e9a3bafd846ced4c345d084b355fb8c7bae75701c338f8a1f8a82c780137826.js defer></script><script>(function(){const e=document.createElement("link");e.href="https://fonts.googleapis.com/css2?family=Lato:wght@300;400;700&display=swap",e.type="text/css",e.rel="stylesheet",document.head.appendChild(e)})()</script></body></html> |