update
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
---
|
||||
title: Obsidian & Quarto setup, current status and questions - Share & showcase - Obsidian Forum
|
||||
source:
|
||||
- https://forum.obsidian.md/t/obsidian-quarto-setup-current-status-and-questions/75003/2
|
||||
author:
|
||||
- "[[Obsidian Forum]]"
|
||||
published: 2024-01-15
|
||||
created: 2024-12-25
|
||||
description: Hi, I am a new user and I have been trying to integrate Obsidian with quarto. quarto is a datascience extension of markdown that renders documents with code into many many publishable formats Here is a 20 sec youtube …
|
||||
tags:
|
||||
- t/clippings
|
||||
- s/obsidian
|
||||
---
|
||||
Hi,
|
||||
|
||||
I am a new user and I have been trying to integrate Obsidian with quarto.
|
||||
|
||||
quarto is a datascience extension of markdown that renders documents with code into many many publishable formats
|
||||
|
||||
Here is a 20 sec youtube video of my current setup:
|
||||
|
||||
[Quarto in Obsidian 241](https://www.youtube.com/watch?v=EJwWlgWFmrA)
|
||||
|
||||
These are the steps I took:
|
||||
|
||||
1. set up a template with the templater plugin so all files I make in the root of the Obsidian fault have a YAML file header that quarto expects. this is the YAMl I use:
|
||||
|
||||
```yaml
|
||||
---
|
||||
title: "Untitled"
|
||||
format: pdf
|
||||
---
|
||||
```
|
||||
|
||||
2. Set up the “execute code” plugin so I can run r/python/etc interactively in Obsidian
|
||||
3. Set up the “shell commands” plugin to run the following command:
|
||||
|
||||
```bash
|
||||
cp {{file_path:absolute}} tmp/{{file_name}}.rmd
|
||||
quarto render tmp/{{file_name}}.rmd --to pdf --output-dir
|
||||
../quarto/
|
||||
rm tmp/{{file_name}}.rmd
|
||||
```
|
||||
|
||||
in a tmp folder the file is rendered, then te output is stored in the quarto folder and the files I made in the process of rendering are deleted.
|
||||
|
||||
3. set up the commander plugin so I have a neat blue (quarto’s color) button that runs the script to render the file I am looking at right now into pdf.
|
||||
|
||||
Things I still have to fing a solution for:
|
||||
|
||||
1. write a script that changes Obsidian style markdown into quarto style markdown to handle internal links and possibly .bib references, and call-outs (which are coded differently)
|
||||
2. make Obsidian highlight r code even if I start a code block with \`\`\`{r} instead of \`\`\`r
|
||||
3. make separate shell scripts to render to other output formats (beamer presentations etc)
|
||||
|
||||
any tips on plugins that would help me solve some of these?
|
||||
|
||||
[](https://forum.obsidian.md/u/echej "echej")
|
||||
|
||||
Nice workaround to render from .md with code included!
|
||||
|
||||
Seems like you’ve already got most of the pieces of this workflow in place. Regarding presentations, [here are 101](https://forum.obsidian.md/t/discontinued-advanced-slides-create-markdown-based-reveal-js-presentations-in-obsidian/28243/244) some details about how I work with Reveal.js presentations in Obsidian using Quarto. It’s quite similar to what you are doing.
|
||||
|
||||
Are you’re familiar with [@echej](https://forum.obsidian.md/u/echej)’s work (qmd-as-md plugin maintainer)?
|
||||
|
||||
He has shared a lot of relevant stuff, like a lua filter that can convert Obsidian callouts into Quarto callouts:
|
||||
|
||||
- [Using Wikilinks and Git/Obsidian Callouts in Quarto Markdown | Daniel Borek 82](https://danielborek.me/2023/obsidian-quarto-callouts/)
|
||||
- This also contains a CSS snippet that makes the Obsidian callouts look the same as the Quarto callouts.
|
||||
- It also contains a lua filter that can remove the non-supported wikilink formatting.
|
||||
|
||||
If you just want to convert the wikilinks into markdown links, there’s several plugins that support that, like [Links 11](https://github.com/mii-key/obsidian-links). You could use this in conjunction with Commander for a seamless workflow.
|
||||
|
||||
As for citations, these are [natively supported through pandoc citekeys 18](https://quarto.org/docs/authoring/footnotes-and-citations.html), so you shouldn’t need to create a script for this - unless I am misunderstanding what you meant by .bib references.
|
||||
|
||||
Regarding the “execute code” plugin, isn’t that a bit redundant when using Quarto, since big selling point of Quarto is exactly it’s ability to execute code when you render or preview documents? Perhaps you can clarify your workflow a bit in this regard.
|
||||
|
||||
I did try the qmd as md plugin, but the qmd files still weren’t fully featured (i.e internal links and tags not registering I think?). Since this is stille a Obsidian centric workflow I’d prefer to work in Obsidian primarily, only opting into quarto when I need/decide to generate output. Ill see if I can include the lua filter in my workflow and set up a second button to co-op your render too reveal workflow.
|
||||
|
||||
WRT citations, I am currently using the “citations” plugin, which means if I cite a paper using their \[\] citation format its a link tot he note page for that citation, while if I render I obviously want to render the pandoc citekey citation and generate a ref list. I guess I could add this to the lua filter and just build the filter out to accommodate?
|
||||
|
||||
WRT the execute code plugin, I was thinking of going even further. I am thinking of forking it and adding an “environment” pane, which would track and display all user generated variables and objects that are generated in chunks that are evaluated. During the writing/coding process I like to be able to run chucks to evaluate their correct and iteratively debug, way faster then rendering out to debug.
|
||||
|
||||
I think my dream is to have Obsidian basically contain a kind of “RStudio light” so I can freely do statistical thinking/note taking. I would only step out of Obsidian and into RStudio or VScode when I decide an idea becomes a scientific paper, that needs full on IDE/version control/high level data security
|
||||
|
||||
Looking at your reveal.js solution I am really digging the use of “quarto preview” and a fixed port, that could be a great alternative tot he code executer route I am now following!
|
||||
|
||||
I guess both would also be an option, but it wouldn’t be easy in my workflow where I am working in a .md, which kind of prevents processing with quarto preview… gotta figure out whether I can somehow create a tmp .qmd to mirror a specific .md and trigger the mirroring and preview with a commander button.
|
||||
|
||||
Gotta say I am i awe of the flexibility build into Obsidian, wish there was an easy way to bundle all my tweaks and selected/required extension into a single setup so I could share the final setup easily.
|
||||
|
||||
Hi [@Feralflora](https://forum.obsidian.md/u/feralflora), [@Michelnivard](https://forum.obsidian.md/u/michelnivard),
|
||||
|
||||
Could you please share an update about your approach to developing, updating, and sharing your (academic) work Obsidian and Quarto?
|
||||
|
||||
Obsidian has been great for me, but sharing progress updates and outputs my research project is difficult at the moment.
|
||||
|
||||
I’m now figuring out how to use the Git-plugin to generate automatic progress updates for ‘project-components’ (i.e. proposal, DMP, chapters, etc.) like release notes in software development. And Quarto seems perfect for sharing outputs and updates in a unified format.
|
||||
|
||||
Hi [@Opi](https://forum.obsidian.md/u/opi),
|
||||
|
||||
I haven’t really made any changes to my Quarto workflow since I posted here. I’m probably going to try out [@Michelnivard](https://forum.obsidian.md/u/michelnivard)’s solution for executing code from regular markdown files by making a temporary intermediary .qmd file.
|
||||
|
||||
I agree that Quarto is perfect for sharing research in a reproducible manner. In addition, I’m looking into using [Typst 24](https://typst.app/) as the typesetting engine in Quarto, which also has a number of templates available.
|
||||
|
||||
Some find the limitations of working with Quarto in Obsidian to be too limiting, and rather opt for working with Quarto files in VS Code, coupled with the [Foam 40](https://foambubble.github.io/foam/) PKM system / extension for VS Code instead. You could check that out too.
|
||||
|
||||
Hi,
|
||||
|
||||
First of all thanks a lot for all your valuable hints!
|
||||
I try to get those pieces together, but with little luck 
|
||||
|
||||
The replacement code for wikilinks didn’t work for me, I use
|
||||
|
||||
```css
|
||||
:gsub("%[%[(.-)%]%]", "[fig](%1)"))
|
||||
```
|
||||
|
||||
Unfortunately though the links are replaced fine images aren’t rendered. I get the links in the pdf like so : ``
|
||||
|
||||
Has anyone an idea, how to solve this?
|
||||
|
||||
Have you specified the [resource-path 8](https://pandoc.org/MANUAL.html#option--resource-path), or transferred the images to the qmd project folder?
|
||||
|
||||
Thanks for spending your time on this! I tried various ways without success:
|
||||
|
||||
1. setting the path relative to the document’s path: “…/pics/test.png”
|
||||
and
|
||||
2. moving the image right into the document’s folder (with the path “test.png”
|
||||
|
||||
Setting a wrong path to the image throws a LaTEX error (“test.png not found”). So the path seems to be correct.
|
||||
|
||||
I had a similar issue trying to render images in a manuscript that lives in a sub-folder of the parent-project. Below fixed the issue for me.
|
||||
|
||||
- Create `_quarto.yml` root of project. It does not need to contain any metadata. (I only use this to experiment with parent-project-level-profiles and links to the project repository on GitHub.
|
||||
- Set resource path in the yaml manuscript project.
|
||||
|
||||
```yaml
|
||||
resource-path:
|
||||
- "130_msc_thesis/notes/"
|
||||
- "130_msc_thesis/images/"
|
||||
- "130_msc_thesis/D03-submit-manuscript/"
|
||||
- "130_msc_thesis/analyse-data/"
|
||||
```
|
||||
|
||||
- include link to image
|
||||
- without cross-referencing
|
||||
|
||||
```css
|
||||

|
||||
```
|
||||
|
||||
- with [cross-referencing using div syntax 3](https://quarto.org/docs/authoring/cross-references-divs.html)
|
||||
|
||||
```markdown
|
||||
:::{#fig-transition-flower-circular-agriculture}
|
||||
|
||||

|
||||
|
||||
This text is a caption for the figure
|
||||
|
||||
:::
|
||||
```
|
||||
|
||||
[](https://forum.obsidian.md/uploads/default/original/3X/9/5/953a23b83b93f7f7b3477d5cdd7a2d801b812dde.png "image")
|
||||
|
||||
Thank you for replying and pointers to Foam and Typst.
|
||||
|
||||
When writing, I found working with Quarto files in Obsidian easier than VS Code with Foam.
|
||||
|
||||
VS Code with ojs code blocks has been great for pulling in data to generate and preview tables. Compiling and previewing work (e.g. graphs > section > manuscript) with VS Code was also incredibly simple, but it did take me a lot of time to understand and fix all inclusion and yaml errors.
|
||||
|
||||
How has you experience with Typst been? I’ve not tried it yet. Learning to work with GitHub, Git, SSH, GPG, VS Code, Quarto, ojs, and mermaid was distracting and challenging enough for now :')
|
||||
|
||||
Yeah, that’s the good thing, you can work with the same files across different programs and utilize their strengths.
|
||||
|
||||
Very good, mostly. Compilation is *very* fast, it is powerful but yet easy to learn. I’m implementing it as the pdf-engine I use in Pandoc and Quarto. Provides nice support for callouts using the “gentle-cues” package and [this filter 12](https://github.com/jgm/pandoc/discussions/9821#discussioncomment-9672291). I had some challenges with getting the citations to work correctly, though. Still working that out.
|
||||
|
||||
Good to hear that it’s fast and great that callouts work in pdf too. I’ll focus Typst instead of LaTex and see if I can get the citations working correctly. Is one citation style not working, or are all styles rendered incorrectly?
|
||||
|
||||
For those who have problem with images in subfolders (useful when I want to be able to compile single chapter in subfolder and the my whole thesis at the same time) and compile to latex/pdf only (my workaround doesnt work for docx or html): add the alternative graphic paths in `_quarto.yml` , in my case it was ` \graphicspath{{figures}{chapters/figures}{../figures}{chapters}{..}}`, also I use `H` option to force figures to stay in given places
|
||||
|
||||
```
|
||||
pdf:
|
||||
link-citations: false
|
||||
number-sections: true
|
||||
reference-section-title: "References"
|
||||
pdf-engine: xelatex
|
||||
fontsize: 12pt
|
||||
include-in-header:
|
||||
text: |
|
||||
%% \usepackage{makeidx}
|
||||
%% \makeindex
|
||||
\usepackage{microtype}
|
||||
\usepackage{epigraph}
|
||||
\usepackage{indentfirst}
|
||||
\setlength{\parindent}{2em}
|
||||
%%\usepackage[british]{babel}
|
||||
%%
|
||||
%% Code related to fonts and how the output looks
|
||||
%%
|
||||
\usepackage{mathpazo}
|
||||
\usepackage[T1]{fontenc}
|
||||
\usepackage[sups,osf]{fbb} % osf (or tosf) for text, not math
|
||||
\usepackage[scaled=.95]{cabin} % sans serif
|
||||
\usepackage[varqu,varl]{inconsolata} % sans serif typewriter
|
||||
%%
|
||||
%% Code related to figures in document
|
||||
%%
|
||||
\usepackage{float}
|
||||
\graphicspath{{figures}{chapters/figures}{../figures}{chapters}{..}}
|
||||
\let\origfigure\figure
|
||||
\let\endorigfigure\endfigure
|
||||
\renewenvironment{figure}[1][2] {
|
||||
\expandafter\origfigure\expandafter[H]
|
||||
} {
|
||||
\endorigfigure
|
||||
}
|
||||
```
|
||||
|
||||
I tried to get this workflow working this morning, and ended up building a little extension that others might find useful. Still waiting to be approved for the official list, but should be able to install it manually - if anybody tests it, let me know if it works!
|
||||
|
||||
Interesting, can you show an example of a syntax conversion that your plugin does? I think such an example would be useful in the readme.
|
424
sources/clippings/Why Type Hinting Sucks! rPython.md
Normal file
424
sources/clippings/Why Type Hinting Sucks! rPython.md
Normal file
@@ -0,0 +1,424 @@
|
||||
---
|
||||
title: "Why Type Hinting Sucks! : r/Python"
|
||||
source:
|
||||
- https://www.reddit.com/r/Python/comments/10zdidm/why_type_hinting_sucks/
|
||||
author:
|
||||
published: 2023-02-11
|
||||
created: 2024-11-27
|
||||
description:
|
||||
tags:
|
||||
- t/clippings
|
||||
- s/informatique/langage/python
|
||||
---
|
||||
up:: [[python type hinting]]
|
||||
|
||||
Type hints are great! But I was [playing Devil's advocate](https://www.reddit.com/r/Python/comments/10vh1v5/mypy_10_released/j7igi8q/?context=3) on a thread recently where I claimed actually type hinting can be legitimately annoying, especially to old school Python programmers.
|
||||
|
||||
But I think a lot of people were skeptical, so let's go through a made up scenario trying to type hint a simple Python package. Go to the end for a TL;DR.
|
||||
|
||||
## The scenario
|
||||
|
||||
This is completely made up, all the events are fictitious unless explicitly stated otherwise (also editing this I realized attempts 4-6 have even more mistakes in them than intended but I'm not rewriting this again):
|
||||
|
||||
**You** maintain a popular third party library `slowadd`, your library has many supporting functions, decorators, classes, and metaclasses, but your main function is:
|
||||
|
||||
```python
|
||||
def slow_add(a, b):
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
You've always used traditional Python duck typing, if a and b don't add then the function throws an exception. But you just dropped support for Python 2 and your users are demanding type hinting, so it's your next major milestone.
|
||||
|
||||
## First attempt at type hinting
|
||||
|
||||
You update your function:
|
||||
|
||||
```python
|
||||
def slow_add(a: int, b: int) -> int:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
All your tests pass, mypy passes against your personal code base, so you ship with the release note "Type Hinting Support added!"
|
||||
|
||||
## Second attempt at type hinting
|
||||
|
||||
Users immediately flood your GitHub issues with complaints! MyPy is now failing for them because they pass floats to `slow_add`, build processes are broken, they can't downgrade because of internal Enterprise policies of always having to increase type hint coverage, their weekend is ruined from this issue.
|
||||
|
||||
You do some investigating and find that MyPy supports [Duck type compatibility](https://mypy.readthedocs.io/en/latest/duck_type_compatibility.html) for `ints -> floats -> complex`. That's cool! New release:
|
||||
|
||||
```python
|
||||
def slow_add(a: complex, b: complex) -> complex:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
Funny that this is a MyPy note and not a PEP standard...
|
||||
|
||||
## Third attempt at type hinting
|
||||
|
||||
Your users thank you for your quick release, but a couple of days later one user asks why you no longer support `Decimal`. You replace `complex` with `Decimal` but now your other MyPy tests are failing.
|
||||
|
||||
You remember Python 3 added [Numeric abstract base classes](https://docs.python.org/3/library/numbers.html), what a perfect use case, just type hint everything as `numbers.Number`.
|
||||
|
||||
Hmmm, MyPy doesn't consider any of integers, or floats, or Decimals to be numbers :(.
|
||||
|
||||
After reading through [typing](https://docs.python.org/3/library/typing.html) you guess you'll just `Union` in the Decimals:
|
||||
|
||||
```python
|
||||
def slow_add(
|
||||
a: Union[complex, Decimal], b: Union[complex, Decimal]
|
||||
) -> Union[complex, Decimal]:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
Oh no! MyPy is complaining that you can't add your other number types to Decimals, well that wasn't your intention anyway...
|
||||
|
||||
More reading later and you try overload:
|
||||
|
||||
```python
|
||||
@overload
|
||||
def slow_add(a: Decimal, b: Decimal) -> Decimal:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: complex, b: complex) -> complex:
|
||||
...
|
||||
|
||||
def slow_add(a, b):
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
But MyPy on strict is complaining that `slow_add` is missing a type annotation, after reading [this issue](https://github.com/python/mypy/issues/3360) you realize that `@overload` is only useful for users of your function but the body of your function will not be tested using `@overload`. Fortunately in the discussion on that issue there is an alternative example of how to implement:
|
||||
|
||||
```python
|
||||
T = TypeVar("T", Decimal, complex)
|
||||
|
||||
def slow_add(a: T, b: T) -> T:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
## Fourth attempt at type hinting
|
||||
|
||||
You make a new release, and a few days later more users start complaining. A very passionate user explains the super critical use case of adding tuples, e.g. `slow_add((1, ), (2, ))`
|
||||
|
||||
You don't want to start adding each type one by one, there must be a better way! You learn about Protocols, and Type Variables, and positional only parameters, *phew*, this is a lot but this should be perfect now:
|
||||
|
||||
```python
|
||||
T = TypeVar("T")
|
||||
|
||||
class Addable(Protocol):
|
||||
def __add__(self: T, other: T, /) -> T:
|
||||
...
|
||||
|
||||
def slow_add(a: Addable, b: Addable) -> Addable:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
## A mild diversion
|
||||
|
||||
You make a new release noting "now supports any addable type".
|
||||
|
||||
Immediately the tuple user complains again and says type hints don't work for longer Tuples: `slow_add((1, 2), (3, 4))`. That's weird because you tested multiple lengths of Tuples and MyPy was happy.
|
||||
|
||||
After debugging the users environment, via a series of "back and forth"s over GitHub issues, you discover that pyright is throwing this as an error but MyPy is not (even in strict mode). You **assume** MyPy is correct and move on in bliss ignoring there is actually a fundamental mistake in your approach so far.
|
||||
|
||||
(**Author Side Note** - It's not clear [if MyPy is wrong](https://github.com/python/mypy/issues/14679) but it defiantly makes sense for Pyright to throw an error here, I've filed issues against both projects and a pyright maintainer has [explained the gory details](https://github.com/microsoft/pyright/issues/4613/) if you're interested. Unfortunately this was not really addressed in this story until the "Seventh attempt")
|
||||
|
||||
## Fifth attempt at type hinting
|
||||
|
||||
A week later a user files an issue, the most recent release said that "now supports any addable type" but they have a bunch of classes that can *only* be implemented using `__radd__` and the new release throws typing errors.
|
||||
|
||||
You try a few approaches and find this seems to best solve it:
|
||||
|
||||
```python
|
||||
T = TypeVar("T")
|
||||
|
||||
class Addable(Protocol):
|
||||
def __add__(self: T, other: T, /) -> T:
|
||||
...
|
||||
|
||||
class RAddable(Protocol):
|
||||
def __radd__(self: T, other: Any, /) -> T:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Addable, b: Addable) -> Addable:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Any, b: RAddable) -> RAddable:
|
||||
...
|
||||
|
||||
def slow_add(a: Any, b: Any) -> Any:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
Annoyingly there is now no consistent way for MyPy to do anything with the body of the function. Also you weren't able to fully express that when b is "RAddable" that "a" should not be the same type because Python type annotations don't yet support being able to exclude types.
|
||||
|
||||
## Sixth attempt at type hinting
|
||||
|
||||
A couple of days later a new user complains they are getting type hint errors when trying to raise the output to a power, e.g. `pow(slow_add(1, 1), slow_add(1, 1))`. Actually this one isn't too bad, you quick realize the problem is your annotating Protocols, but really you need to be annotating Type Variables, easy fix:
|
||||
|
||||
```python
|
||||
T = TypeVar("T")
|
||||
|
||||
class Addable(Protocol):
|
||||
def __add__(self: T, other: T, /) -> T:
|
||||
...
|
||||
|
||||
A = TypeVar("A", bound=Addable)
|
||||
|
||||
class RAddable(Protocol):
|
||||
def __radd__(self: T, other: Any, /) -> T:
|
||||
...
|
||||
|
||||
R = TypeVar("R", bound=RAddable)
|
||||
|
||||
@overload
|
||||
def slow_add(a: A, b: A) -> A:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Any, b: R) -> R:
|
||||
...
|
||||
|
||||
def slow_add(a: Any, b: Any) -> Any:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
## Seventh attempt at type hinting
|
||||
|
||||
Tuple user returns! He says MyPy in strict mode is now complaining with the expression `slow_add((1,), (2,)) == (1, 2)` giving the error:
|
||||
|
||||
> Non-overlapping equality check (left operand type: "Tuple\[int\]", right operand type: "Tuple\[int, int\]")
|
||||
|
||||
You realize you can't actually guarantee anything about the return type from some arbitrary `__add__` or `__radd__`, so you starting throwing `Any` Liberally around:
|
||||
|
||||
```python
|
||||
class Addable(Protocol):
|
||||
def __add__(self: "Addable", other: Any, /) -> Any:
|
||||
...
|
||||
|
||||
class RAddable(Protocol):
|
||||
def __radd__(self: "RAddable", other: Any, /) -> Any:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Addable, b: Any) -> Any:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Any, b: RAddable) -> Any:
|
||||
...
|
||||
|
||||
def slow_add(a: Any, b: Any) -> Any:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
## Eighth attempt at type hinting
|
||||
|
||||
Users go crazy! The nice autosuggestions their IDE provided them in the previous release have all gone! Well you can't type hint the world, but I guess you could include type hints for the built-in types and *maybe* some Standard Library types like Decimal:
|
||||
|
||||
You think you can rely on some of that MyPy duck typing but you test:
|
||||
|
||||
```python
|
||||
@overload
|
||||
def slow_add(a: complex, b: complex) -> complex:
|
||||
...
|
||||
```
|
||||
|
||||
And realize that MyPy throws an error on something like `slow_add(1, 1.0).as_integer_ratio()`. So much for that nice duck typing article on MyPy you read earlier.
|
||||
|
||||
So you end up implementing:
|
||||
|
||||
```python
|
||||
class Addable(Protocol):
|
||||
def __add__(self: "Addable", other: Any, /) -> Any:
|
||||
...
|
||||
|
||||
class RAddable(Protocol):
|
||||
def __radd__(self: "RAddable", other: Any, /) -> Any:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: int, b: int) -> int:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: float, b: float) -> float:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: complex, b: complex) -> complex:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: str, b: str) -> str:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: tuple[Any, ...], b: tuple[Any, ...]) -> tuple[Any, ...]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: list[Any], b: list[Any]) -> list[Any]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Decimal, b: Decimal) -> Decimal:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Fraction, b: Fraction) -> Fraction:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Addable, b: Any) -> Any:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Any, b: RAddable) -> Any:
|
||||
...
|
||||
|
||||
def slow_add(a: Any, b: Any) -> Any:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
As discussed earlier MyPy doesn't use the signature of any of the overloads and compares them to the body of the function, so all these type hints have to manually validated as accurate by you.
|
||||
|
||||
## Ninth attempt at type hinting
|
||||
|
||||
A few months later a user says they are using an embedded version of Python and it hasn't implemented the Decimal module, they don't understand why your package is even importing it given it doesn't use it. So finally your code looks like:
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import TYPE_CHECKING, Any, Protocol, TypeVar, overload
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from decimal import Decimal
|
||||
from fractions import Fraction
|
||||
|
||||
class Addable(Protocol):
|
||||
def __add__(self: "Addable", other: Any, /) -> Any:
|
||||
...
|
||||
|
||||
class RAddable(Protocol):
|
||||
def __radd__(self: "RAddable", other: Any, /) -> Any:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: int, b: int) -> int:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: float, b: float) -> float:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: complex, b: complex) -> complex:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: str, b: str) -> str:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: tuple[Any, ...], b: tuple[Any, ...]) -> tuple[Any, ...]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: list[Any], b: list[Any]) -> list[Any]:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Decimal, b: Decimal) -> Decimal:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Fraction, b: Fraction) -> Fraction:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Addable, b: Any) -> Any:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Any, b: RAddable) -> Any:
|
||||
...
|
||||
|
||||
def slow_add(a: Any, b: Any) -> Any:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
||||
## TL;DR
|
||||
|
||||
Turning even the simplest function that relied on Duck Typing into a Type Hinted function that is useful can be painfully difficult.
|
||||
|
||||
Please always put on your empathetic hat first when asking someone to update their code to how you think it should work.
|
||||
|
||||
In writing up this post I learnt a lot about type hinting, please try and find edge cases where my type hints are wrong or could be improved, it's a good exercise.
|
||||
|
||||
**Edit:** Had to fix a broken link.
|
||||
|
||||
**Edit 2:** It was late last night and I gave up on fixing everything, some smart people nicely spotted the errors!
|
||||
|
||||
I have a "tenth attempt" to address these error. But pyright complains about it because my overloads overlap, however I don't think there's a way to express what I want in Python annotations without overlap. Also Mypy complains about some of the user code I posted earlier giving the error [comparison-overlap](https://mypy.readthedocs.io/en/stable/error_code_list2.html#check-that-comparisons-are-overlapping-comparison-overlap), interestingly though pyright seems to be able to detect here that the types don't overlap in the user code.
|
||||
|
||||
I'm going to file issues on pyright and mypy, but fundamentally they might be design choices rather than strictly bugs and therefore a limit on the current state of Python Type Hinting:
|
||||
|
||||
```python
|
||||
T = TypeVar("T")
|
||||
|
||||
class SameAddable(Protocol):
|
||||
def __add__(self: T, other: T, /) -> T:
|
||||
...
|
||||
|
||||
class Addable(Protocol):
|
||||
def __add__(self: "Addable", other: Any, /) -> Any:
|
||||
...
|
||||
|
||||
class SameRAddable(Protocol):
|
||||
def __radd__(self: T, other: Any, /) -> T:
|
||||
...
|
||||
|
||||
class RAddable(Protocol):
|
||||
def __radd__(self: "RAddable", other: Any, /) -> Any:
|
||||
...
|
||||
|
||||
SA = TypeVar("SA", bound=SameAddable)
|
||||
RA = TypeVar("RA", bound=SameRAddable)
|
||||
|
||||
@overload
|
||||
def slow_add(a: SA, b: SA) -> SA:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Addable, b: Any) -> Any:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Any, b: RA) -> RA:
|
||||
...
|
||||
|
||||
@overload
|
||||
def slow_add(a: Any, b: RAddable) -> Any:
|
||||
...
|
||||
|
||||
def slow_add(a: Any, b: Any) -> Any:
|
||||
time.sleep(0.1)
|
||||
return a + b
|
||||
```
|
||||
|
Reference in New Issue
Block a user