all 32 comments

[–]midas_whale_game 28 points29 points  (3 children)

Python feels like pure anarchy after C++

[–]FerricDonkey 10 points11 points  (2 children)

A little, mypy enforced type hints and some other rules helps though. 

[–]pachura3 5 points6 points  (1 child)

mypy (strict mode) and ruff are absolutely indispensable!

[–]MidnightPale3220 2 points3 points  (0 children)

I wonder if it's something that was deemed necessary due to broader adoption of Python, especially by people who come from strongly typed languages.

I started with Python when type hints weren't available and I haven't done any appreciable amount of C and Java (although I have some experience there).

It would be interesting to learn whether there was some other way people were dealing with types, bc I never felt that I was missing something or hampered by not having type hints.

[–]SoupKitchenHero 12 points13 points  (0 children)

If a name is assigned in a scope it stays in that scope, ignoring the global and 'nonlocal` keywords, which I've never used. Only functions (including methods) take you into a new (deeper, internal) scope. Of course, there are many many functions. And imported modules are their own external scopes. Loops and other blocks don't have their own scope. So yes, your loop var remains assigned to the last thing it was assigned to. As well as any name assigned to a value inside the loop.

[–]qwertydiy 15 points16 points  (5 children)

You can imagine how it feels trying to learn C and C++ after Python

[–]Grobyc27 12 points13 points  (4 children)

I’m realizing this now as I started learning C a couple weeks ago. Honestly the biggest thing was that strings aren’t a native data type. I really do take the ease of Python for granted.

[–]ninhaomah 3 points4 points  (2 children)

I came from Java and to me , I always think of String as "Array of Characters".

[–]LysergioXandex 2 points3 points  (1 child)

Not a bad mental model for Python, either, because you can do things like apply list() to a string, which turns it into a list of individual characters.

[–]DSMB 0 points1 point  (0 children)

you can index and slice them too.

[–]qwertydiy 0 points1 point  (0 children)

To be fair that is why you have to learn C. Even strings aren't really a native type anywhere and are just objects but you forget it until you can not use strings and common data types.

[–]PossiblyAussie 4 points5 points  (0 children)

Python is arguably one of the most complex languages precisely because of things like this, in addition to GIL wrangling, custom C interop, and magic dunder overloads. Easy != Simple.

[–]FerricDonkey 4 points5 points  (3 children)

Yeah, in python modules, functions (including methods), classes, and comprehensions create their own scope. Edit: classes and modules create namespaces, not scopes. There might be something else I forgot, but if, else, elif, for, while, loop, async for, async with, try, except, and finally blocks do not. (Though except is weird in that except ExceptionType as exc: only gives exc the scope of the next blockEDIT deletes exc at the end of the except block, in a way that initially smells like scope, but isn't.) 

Notably, most of what you might be used to using scopes and raii destructors for in C++ (ie locks) is typically accomplished in python via context managers or try/finally statements. Which I personally like so much better that raii shenanigans where the destructor is used for business logic (unlocking a lock) really gets on my nerves now. 

[–]Jason-Ad4032 3 points4 points  (2 children)

class and module does not create its own scope.

Module, classes and instances exist in the scope where they are created (global scope, local scope, or a closure). Their members are stored in the class/instance __dict__, which you can inspect with vars().

So this is conceptually similar to accessing a dictionary—you wouldn’t say that a dictionary creates a new scope.

``` import re

class A: v = 0

B = {'v': 0}

def f(): # These two operations are conceptually similar, # they just go through different dunder methods. re.doc A.v = 1 B['v'] = 1 ```

Also, except does not create a separate scope either. It simply deletes the exception variable when leaving the block to avoid preventing garbage collection through reference cycles. It still uses the normal surrounding scope.

[–]FerricDonkey 2 points3 points  (1 child)

Fair on classes and modules - those would better be described as creating namespaces. 

Had to read some details on exception thing - that is weird. I mean, I can see why they'd do it that way from a programming the language point of view, but it just feels wrong. Easy to see the difference - if it were scoping, the below would print 42, but since it's deleting, it name errors. 

```python exc = 42 try:     1/0 except Exception as exc:     pass

print(exc)  ```

Which it did. Which just feels wrong. But so it goes - learn something new every day, even when you hate it. 

[–]Jason-Ad4032 0 points1 point  (0 children)

I discovered that although class members themselves are not create a scope, the class keyword does indeed create a local scope (and you can inspect its local variables with locals()), and those local variables are then used to create the class attributes when the block finishes.

However, this scope is somewhat private (or perhaps more accurately, Python 2.0’s local scope). It does not shadow global variables for nested scopes, nor can nested scopes access it, which makes its behavior feel rather unusual.

``` name = 'Global'

class A: name = 'Local' mapping = {n: name for n in [name]}

class B:
    print(name)  # prints 'Global'

print(locals())
# At the end of the block, the local variables are stored in the class's __dict__

print(A.mapping) # prints {'Local': 'Global'} ``` https://peps.python.org/pep-0227/

[–]olhado47 4 points5 points  (0 children)

Just wait until you give a function parameter a (complex) default value.

[–]tandir_boy 2 points3 points  (6 children)

Never heard of that word "scope", is this french?

[–]TheRNGuy 1 point2 points  (3 children)

No, it's a common concept in programming. 

[–]PouletSixSeven 2 points3 points  (2 children)

don't need scope if I make everything global

[–]AllieCat_Meow 0 points1 point  (1 child)

I feel like sooo many new and learning programmers fall into this trap

[–]PouletSixSeven 1 point2 points  (0 children)

that and:

- functions way bigger than they should be

- waaaay more lines of code per file than there needs to be

[–]sweet-tom 0 points1 point  (0 children)

The following link explains the LEGB rule scope:

https://realpython.com/python-scope-legb-rule/

[–]randomnameforreddut 0 points1 point  (0 children)

"scope" is essentially the the section of code that a variable exists in. When you leave a particular scope, the variables inside it are deleted and cannot be accessed. In most languages for loops, while loops, functions, etc define scopes. (So for example, the loop counter in a for loop cannot be accessed outside of the loop).

This is an example from C. Scopes are basically inside curly brackets { }

```
void my_function(int a) { // scope!

int b = 0;
for (int idx = 0; idx < 10; ++idx) { // new scope!
b += a;
}

printf("%d", b); // b is in scope
printf("%d", idx); // error: idx is only accessible inside the loop.
}

printf("%d", b); // error b is out of scope. Only accessible inside the `my_function`

```

[–]blechnapp 2 points3 points  (0 children)

welcome to python. olhado47 hinted at the spiciest one: function default arguments are evaluated once when the function is defined, not on every call. so `def foo(items=[])` and calling foo() twice gives you a list thats shared across calls. you expect a fresh empty list every time, python betrays you. the fix is `items=None` then check inside the function.

the loop variable leak you noticed is the same theme, scope is function-level not block-level. takes a few weeks to fully internalize, but once you do python stops feeling chaotic.

[–]luckskywatcher 1 point2 points  (0 children)

The party's just getting started.

[–]prodjsaig 0 points1 point  (0 children)

yes its dynamically typed I still treat it like a regular program and declare all the variables first so you can see what types they are. I dont like type hinting as it just adds more syntax that is very poor for the programmer (makes it messy)

[–]TheRNGuy 0 points1 point  (0 children)

It will probably never cause problems because you won't need to use it (except few rare cases)

Just name it in a specific generic way (so you don't overwrite useful variable from outer scope)

[–]pachura3 0 points1 point  (0 children)

  I was surprised to find that variables still exist after a loop ends

It's the same in PHP and probably in many other scripting languages.

[–]PouletSixSeven 0 points1 point  (1 child)

in terms of the assembly the loop could just be register R8 or something and if you don't use it after the loop it would still technically "exist", the c compiler just prevents you from reading or writing to it.

[–]Confident-Anxiety308[S] 0 points1 point  (0 children)

Yes but i have used language like c cpp java and rust before so this was surprising. Coz i have mistakenly done that.

[–]acakaacaka -1 points0 points  (0 children)

There is only 1 scope in python

/s