Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

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.



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


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?


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)


Ah, thanks. That's short and does almost what I want.


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.


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.


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)


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 :)


i meant exec as in a shell script to replace the current PID, not python exec()


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.


  > 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


It needs to also do:

   from __future__ import absolute_import, division, print_function, unicode_literals
This will give you syntax much closer to Python 3 in Python 2.7. The remaining differences can be papered over with function and class helpers. Well, except for metaclasses, but I doubt you'd have need for those in a single-file script.


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.


ahh, I see now. I would add an edit to my original comment, but it's too late.


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.


There's an interesting extensibility point in Python in form of codecs and the encoding comment:

   # -*- coding: ... -*-
The trick is that encoding names come from an extensible registry of codecs (https://docs.python.org/3/library/codecs.html#codecs.registe...), so you can register your own. And when you look at codec API, you get the raw byte stream as input, and spit out strings - so it's basically as powerful as reader macros.

The problem is getting that registration code to run before your script starts. Easy if you have multiple scripts, but it requires .pth hacks if you go for single-file.


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.


Ah, good catch. They do work in Perl, perhaps in Ruby also.


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)


>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.


Disagree. The breaking changes then were a tough pill. More would not have been better.


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.


Excellent point.


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.


Yes, I didn't mean for libraries, but rather for single file scripts.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: