'process exited with status 1'
If it hadn't already been flagged, that is.
("Lmao" is useless, but definitely not worse than some other responses.)
if not (API_KEY := os.getenv("API_KEY")):
...
For internal tools I just let os.environ["API_KEY"] raise a KeyError. It's descriptive enough. try:
x = int('cat')
except Exception as e:
pass
print(e) # <- NameError: name 'e' is not defined
So, it appears Python actually has three variable scopes (global, local, exception block)?It's not that odd, since it's the only situation where you cannot keep it bounded, unless you enjoy having variables that may or may not be defined (Heisenberg variable?), depending on whether the exception has been raised or not?
Compare with the if statement, where the variable in the expression being tested will necessarily be defined.
if False:
x = 7
print(x)
print(x)
^
NameError: name 'x' is not defined
Ruby does this sort of stuff, where a variable is defined more or less lexically (nil by default). Python doesn't do this. You can have local variables that only maybe exist in Python. for i in range(0):
passIs there any chance this would cause trouble though? Furthermore, what would be the need of having this variable accessible after the except block? In the case of a for block, it could be interesting to know at which point the for block was "passed".
So, maybe "None" answers your question?
if <true comparison here>:
x = 5
print(x) # <- should give name error?In the case of an if like in your example, no provision is made about the existence of x. It could have been defined earlier, and this line would simply update its value.
Your example:
if True:
x = 5
print(x) # 5
Same with x defined prior: x = 1
if False:
x = 5
print(x) # 1
What about this one? if False:
x = 5
print(x) # ???
On the other hand, the notation "<exception value> as <name>" looks like it introduces a new name; what if that name already existed before? Should it just replace the content of the variable? Why the "as" keyword then? Why not something like "except <name> = <exception value>" or the walrus operator?While investigating this question, I tried the following:
x = 3
try:
raise Exception()
except Exception as x:
pass
print(x) # <- what should that print? e = 'before'
try:
x = int('cat')
except Exception as e:
e2 = e
print(e)
print(e2) # <- This works!
print(e) # <- NameError: name 'e' is not defined
It's not a scoping thing, the bound exception variable is actually deleted after the exception block, even if it was already bound before!edit: writing from phone on couch and the laptop... looks far, far away...
https://stackoverflow.com/questions/24271752
https://docs.python.org/3/reference/compound_stmts.html#exce...
But the language makes sense at a lower level, scopes, values, bindings have their mostly reasonable rules that are not hard to follow.
In comparison python seems like an infinite tower of ad-hoc exceptions over ad-hoc rules, sure it looks simpler but anywhere you look you discover an infinite depth of complexity [1]
[0] and how half of the complaints are a conjugation of "I don't like that NaNs exist
[1] my favourite example is how dunder methods are a "synchronized view" of the actual object behaviour, that is in a + b a.__add__ is never inspected, instead at creation time a's add behaviour is defined as its __add__ method but the association is purely a convention, eg any c extension type need to reimplement all these syncs to expose the correct behaviour and could for funzies decide that a type will use __add__ for repr and __repr__ for add
The "random things" make it practically impossible to figure out what will happen without learning a whole bunch of seemingly arbitrary, corner-case-specific rules (consider the jsdate.wtf test currently making the rounds). And no, nobody is IMX actually simply complaining about NaNs existing (although the lack of a separate integer type does complicate things).
Notice that tests showcasing JavaScript WTFery can work just by passing user data to a builtin type constructor. Tests of Python WTFery generally rely on much more advanced functionality (see e.g. https://discuss.python.org/t/quiz-how-well-do-you-know-pytho...). The only builtin type constructor in Python that I'd consider even slightly surprising is the one for `bytes`/`bytearray`.
Python's scoping is simple and makes perfect sense, it just isn't what you're used to. (It also, unlike JavaScript, limits scope by default, so your code isn't littered with `var` for hygiene.) Variables are names for objects with reference semantics, which are passed by value - exactly like `class` types in C# (except you don't have to worry about `ref`/`in`/`out` keywords) or non-primitives in Java (notwithstanding the weird hybrid behaviour of arrays). Bindings are late in most places, except notably default arguments to functions.
I have no idea what point you're trying to make about __add__; in particular I can't guess what you think it should mean to "inspect" the method. Of course things work differently when you use the C API than when you actually write Python code; you're interacting with C data structures that aren't directly visible from Python.
When you work at the Python level, __add__/__iadd__/__radd__ implement addition, following a well-defined protocol. Nothing happens "at creation time"; methods are just attributes that are looked up at runtime. It is true that the implementation of addition will overlook any `__add__` attribute attached directly to the object, and directly check the class (unlike code that explicitly looks for an attribute). But there's no reason to do that anyway. And on the flip side, you can replace the `__add__` attribute of the class and have it used automatically; it was not set in stone when the class was created.
I'll grant you that the `match` construct is definitely not my favourite piece of language design.
At runtime when evaluating a + b no dunder method is looked up and there is no guarantee that a + b === a.__anydunder__(b) https://youtu.be/qCGofLIzX6g
What i mean with weird scoping is
def foo():
e = 'defined'
try:
raise ValueError
except Exception as e:
print(e)
print(e) # this will error out
foo()
I also dislike how local/global scopes work in python but that is more of a personal preference.I agree that that Javascripts standard library is horrible the jsdate.wtf is an extreme but apt example, IMO most of these are solved with some "defensive programming" but I respect other opinions here.
> And no, nobody is IMX actually simply complaining about NaNs existing
I watched many Javascript WTF! videos on youtube and NaNs and [2] == "2" were usually 90% of the content.
Python really is a bit of a mess haha.
But if something fails in a loop running in the repl or jupyter I already have access to the variables.
If I want to do something with a loop of data that is roughly the same shape, I already have access to one of the the items at the end.
Short circuiting/breaking out of a loop early doesn't require an extra assignment.
I really can't see the downside.
This "feature" was responsible for one of the worst security issues I've seen in my career. I love Python, but the scope leakage is a mess. (And yes, I know it's common in other languages, but that shouldn't excuse it.)
1) Loop through a list of permissions in a for list
2) After the loop block, check if the user had a certain permission. The line of code performing the check was improperly indented and should have failed, but instead succeeded because the last permission from the previous loop was still in scope.
Fortunately there was no real impact because it only affected users within the same company, but it was still pretty bad.
>>> s = "abc"
>>> [x:=y for y in s]
['a', 'b', 'c']
>>> x
'c'
>>> y
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'y' is not defined
Comprehensions have their own local scope for their local variables, but the walrus operator reaches up to the innermost "assignable" scope.The last time I wrote Python in a job interview, one of the interviewers said "wait, I don't know Python very well but isn't this kinda an old style?" Yes, guilty. My Python dates me.
Really nice to combine 1) checking if something exists and 2) act on it
Letting the KeyError go is usually fine, but if you want to log it or can recover somehow then:
try:
API_KEY = os.environ["API_KEY"]
except KeyError:
logger.exception("...")
...Sometimes that's inevitable, bit noisy of the time it isn't.
If the user is a typical consumer using a typical consumer interface, then yes you want to handhold them a bit more.
You can protect passwords in a password manager. You do not need to keep the passwords in env and I do not.
Why are processes running that can do this, that I don't already fully trust?
> You can protect passwords in a password manager.
What's your plan for supplying the password to the program, given that people will want to automate use of the program?
https://typer.tiangolo.com/tutorial/arguments/envvar/
It's especially nice for secrets. Best of both worlds :)
One could write a huge treatise on everything that is wrong with environment variables. Avoid them like the plague. They are a huge usability PITA.
Environment variables are substantially more secure than plain text files because they are not persistent. There are utilities for entering secrets into them without leaking them into your shell history.
That said, you generally should not use an environment variable either. You should use a secure temporary file created by your shell and pass the associated file descriptor. Most shells make such functionality available but the details differ (ie there is no fully portable approach AFAIK).
The other situation that sometimes comes up is that you are okay having the secret on disk in plain text but you don't want it inadvertently commited to a repository. In those cases it makes sense to either do as you suggested and have a dedicated file, or alternatively to set an environment variable from ~/.bashrc or similar.
exit("Missing ...")
This prints the message and exits with code 1. if not API_KEY and not CHANNEL_ID:
print("Missing both YOUTUBE_API_KEY and YOUTUBE_CHANNEL_ID.")
exit(1)
if not API_KEY:
print("Missing YOUTUBE_API_KEY.")
exit(1)
if not CHANNEL_ID:
print("Missing YOUTUBE_CHANNEL_ID.")
exit(1)
That way you don't end up fixing one just come back and be told you're also missing another requirement valid = True
if not API_KEY:
print("Missing YOUTUBE_API_KEY.")
valid = False
if not CHANNEL_ID:
print("Missing YOUTUBE_CHANNEL_ID.")
valid = False
if not valid:
exit(1)
This way you only check each value once (because your logic might be more complicated than just checking it's not set, maybe it can be wrongly formatted) and you still get to do whatever logic you want. It also removed the combinatorial problems.This is a pretty general principle of separating decision from action.
$ python3 -c "print('clear messaging'); exit(1)"
clear messaging
$ python3 -c "raise ValueError('text that matters')"
Traceback (most recent call last):
File "<string>", line 1, in <module>
ValueError: text that matters
and that story gets _a lot_ worse when some programs raise from within a "helper" module and you end up with 8 lines of Python junk to the one line of actual signal API_KEY = os.environ.get("YOUTUBE_API_KEY")
CHANNEL_ID = os.environ.get("YOUTUBE_CHANNEL_ID")
assert(API_KEY, "Missing YOUTUBE_API_KEY")
assert(CHANNEL_ID, "Missing CHANNEL_ID")That bit me before... it created a tuple which evaluated as true.
if not API_KEY:
sys.exit("Missing YOUTUBE_API_KEY.")
It will print message to sys.stderr and do system exit with code 1.P.S. exit() is just an alias to sys.exit(), I prefer longer form.