[HN Gopher] Show HN: Python library to overload functions based ...
___________________________________________________________________
Show HN: Python library to overload functions based on interpreter
version
Author : ttymck
Score : 43 points
Date : 2021-10-19 05:00 UTC (1 days ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| goodside wrote:
| I don't want to discourage the author, but this seems like a
| misguided use of decorators. Maybe there's some scenario where
| it's practical to do version checks only at function boundaries,
| but why limit yourself that? It's already easy (and clear, and
| non-magical) to check sys.version yourself in an if-statement.
| You can do that inside a def block to modify a function slightly,
| or _outside_ a def block if you truly need the function
| definition to change by version. Given that, I don't see how this
| library adds enough value to justify it as a dependency.
|
| The project should also maybe note that it doesn't help at all
| with Python 2 compatibility. A newer Python user might assume it
| enables freely mixing Python 2 and Python 3 in the same file, but
| even without reading the code I'm pretty sure the library doesn't
| (and couldn't) support this.
| sebst wrote:
| That looks great, maybe some libraries can leverage this to offer
| a wider range of compatibility -- even if "recent" syntax
| changes, such like the walrus operator or the match/case
| statements cannot be covered by this.
|
| Btw, I've taken the liberty to post your project to my Python
| News aggregator here:
| https://news.python.sc/item/ff8f92de-666e-4913-8aa3-27af90df...
| remram wrote:
| I'm not sure I get the point of this. If you already have to
| write the code using the equivalent old syntax, and keep
| maintaining it, why write the code a second time with the new
| syntax?
| theptip wrote:
| Old versions get deprecated in libraries, you don't just cut
| over to a new way and break your callers on upgrade.
|
| If you own all the callers of your api in the same repo, you
| probably don't want this, you just replace all the old callers
| along with the implementation.
| remram wrote:
| What I mean is (looking at the example in the README): when
| `asyncio.run()` was added,
| `asyncio.get_event_loop().run_until_complete()` didn't stop
| working.
|
| What is the point of making two versions of the functions,
| when one of them already supports every Python you target?
| kuratkull wrote:
| The ecosystem is a bit broken, but the owners want to use
| the latest stuff. The outcome is obvious.
| ltbarcly3 wrote:
| If Python had macros this library would have a very different
| implementation indeed.
|
| I think 200LOC and a complex registration/namespace/cache system
| is a bit much to avoid an if statement around a def for a handful
| of functions. This is especially true since this just turns the
| if into a decorator, and imports will still need to be done the
| old way for compatibility. The cognitive overhead of this is not
| a good tradeoff.
| kazinator wrote:
| If Python simply parsed and evaluated top-level expressions one
| by one, you could test the value of some version variable and
| act accordingly. Like for this range of versions, load this
| file, otherwise load that one.
|
| If I think of this problem in a Lisp frame, it does not scream
| "use macros" at me.
| ttymck wrote:
| You're absolutely right! I built this mainly to see if it could
| be done. I noticed uvicorn's main function uses an if-statement
| like you described, and I wondered what it would look like as a
| decorator.
|
| My next goal is to see if I can make the resolution "static",
| i.e. Once all the functions are loaded, avoid the dictionary
| lookup at runtime, and just assign the appropriate function
| version as the bare module attribute.
|
| It's fun to do things like this in python, simply because you
| can. But it's also important to point out that there's a lot of
| things you _shouldn't_ (this may be one of them!). I'm glad to
| see this sparked some discussion!
| tyingq wrote:
| I wish Python had come with some equivalent to the "BEGIN {}"
| blocks that Perl and Awk have.
|
| Lacking that, there's no way to write a (single file) script
| where a Python 2 interpreter would just get a "You need Python 3"
| error instead of a syntax error. Because testing for the
| interpreter version happens too late...after the syntax error
| already happened.
|
| Perl's "BEGIN {}" block lets you easily test for versions or
| language features before the script itself is parsed.
| smitty1e wrote:
| Sure to please ~17% of the people, PEP 638 -- Syntactic Macros
| => https://www.python.org/dev/peps/pep-0638/
|
| (It's only Standards Track at the moment)
| tyingq wrote:
| >Created: 24-Sep-2020
|
| I imagine it would have been more popular than 17% if it were
| available when v2 -> v3 pain was at the high point.
| smitty1e wrote:
| Disagree. The breaking changes then were a tough pill. More
| would not have been better.
| tyingq wrote:
| I wasn't proposing adding yet another change at the time.
| I'm saying if the capability were already in place,
| dealing with breaking changes would have been easier.
|
| The whole reason this thought popped in my head is that I
| still have people asking today: _" Tried to run your
| script, and I got a syntax error...is it broken?"_. When
| the issue is that the default python on their box is 2.x.
| smitty1e wrote:
| Excellent point.
| jwilk wrote:
| Not ideal, but in my code, I intentionally provoke syntax error
| near the beginning of the file, with a comment explaining
| what's going on, e.g.: 0_0 # Python >= 3.6
| is required
|
| I have a collection of such incantations for many Python
| versions here:
|
| https://github.com/jwilk/python-syntax-errors
| tyingq wrote:
| Ah, thanks. That's short and does almost what I want.
| neoncontrails wrote:
| That's hilarious. Which PEP made 0_0
|
| syntactically okay? I typed it into an AST visualizer (I'm on
| mobile) and if I'm not mistaken this is equivalent to `0`.
| But why?
| kuratkull wrote:
| Underscores in Numeric Literals. (PEP 515)
| https://www.python.org/dev/peps/pep-0515/
|
| Your question about 0_0 is just 00 which is 0.
|
| (This is really common in other modern languages. Readable
| example: 1_000_000 vs 1000000)
| ltbarcly3 wrote:
| You couldn't be more mistaken, most libraries supported both
| python 2 and 3 for years. Doing what you describe is extremely
| trivial. If you thought it was a good idea you could write a
| script that would exec trampoline itself into a different
| interpreter version entirely.
|
| You can also catch a SyntaxError like any other exception.
| tyingq wrote:
| Put an f-string into a script and get python2 not to choke on
| it, or try catching that SyntaxError as you
| describe...without using eval, futures, or similar. You can't
| catch something that already happened.
|
| Yes, people are able to write backwards compatible scripts...
| by avoiding some features, or by using more than a single
| file.
|
| What I'm describing was just a desire for a very simple "this
| script requires python version >= X" error, with the ability
| to write unfettered python version X code below it, in a
| single file script.
| emidln wrote:
| This isn't pretty, but it's possible:
| import sys if sys.version_info[0] < 3:
| print("Needs Python 3+") sys.exit(1) else:
| prog = r''' def help(): print("Help:
| ...") def do_spam(*args):
| print("spamming") def main(args):
| match args: case [subcommand, *args]:
| return globals()[f'do_{subcommand}'](*args)
| case _: return help() if
| __name__ == '__main__': try: status
| = main(sys.argv[1:]) sys.exit(int(status if
| status is not None else 0)) except Exception as
| e: import traceback
| traceback.print_exc(e) sys.exit(1) '''
| compile(prog, '', 'exec') exec(prog)
| tyingq wrote:
| Sure, a variation on eval. I still think some sort of
| BEGIN equivalent would have been nice. But, the optimal
| time for that has passed, so I'm just complaining anyway
| :)
| dec0dedab0de wrote:
| _What I 'm describing was just a desire for a very simple
| "this script requires python version >= X" error, with the
| ability to write unfettered python version X code below it,
| in a single file script._ import sys
| if sys.version_info.major < 3: raise
| Exception("this script requires python version >= 3")
|
| plus an extra line or two if you're worried about minor
| versions.
| tyingq wrote:
| Now put an f-string (or other new feature that pops a
| syntax error) below that:
|
| print(f"whee")
|
| And run it with python2, or a python3 < 3.6.
| dec0dedab0de wrote:
| ahh, I see now. I would add an edit to my original
| comment, but it's too late.
| jaster wrote:
| > cat test.py import sys if
| sys.version_info.major < 3: raise
| Exception("this script requires python version >= 3")
| print(f"test") > python2 test.py
| File "test.py", line 5 print(f"test")
| ^ SyntaxError: invalid syntax
|
| Doesn't seem that simple
| [deleted]
| BiteCode_dev wrote:
| My way to work around this is to make a zipapp that contains
| several files, hence the entry point can do the checking and
| import another file that contains the code. But since its a
| zipapp, it behaves like a single file script:
| https://docs.python.org/3/library/zipapp.html
|
| Nowaday, I use shiv to produce the zipapp:
|
| https://shiv.readthedocs.io/en/latest/
|
| It's handy because you can also now embed 3rd party
| dependencies, even for small quick scripts if you wish.
| villasv wrote:
| It doesn't seem common to write single file libraries in
| Python; and it doesn't seem to be too unreasonable to add an
| entrypoint script for version checks.
| tyingq wrote:
| Yes, I didn't mean for libraries, but rather for single file
| scripts.
| kazinator wrote:
| Awk's BEGIN blocks are not parsed separately from the rest of
| the program. Awk's Yacc-based implementations (like the
| original One True Awk and GNU Awk) parse the entire input in a
| single call to yyparse() before executing anything.
|
| Therefore, I suspect it is not possible to write a test in the
| BEGIN block which avoids a version-dependent syntax error
| elsewhere in favor of terminating with a graceful error.
| tyingq wrote:
| Ah, good catch. They do work in Perl, perhaps in Ruby also.
___________________________________________________________________
(page generated 2021-10-20 23:02 UTC)