The biggest gap I see so far has nothing to do with lists, or loops, or procedural matters of any kind. Step back from that to Python’s values, its data types, and try to see them as Python does, in expressions.
Every computation (expression) has a result of some type. Understanding which type that is, how values of that type behave, and what you can do with values of that type, those are some of the “key basic concepts” you’ve been missing.
Types are the building blocks to nearly everything else in Python, and, by extension, in Anvil. So they make a very good starting point.
Python has a very rich set of types, each with a rich set of features, so it’s easy to get lost in that maze, if you try to rush through it. Feel free to take it one digestible chunk at a time, experiment with it, see how it works.
And don’t worry about remembering every little detail. The Python on-line reference is a terrific resource. I still refer to it frequently, even after years of practice!
A separate pillar of the language would be names. Nearly everything in a Python program has a name.
For lookup purposes, names are grouped into name-spaces. This is essential. It helps keep different parts of your code from stepping on each others’ variables! That may be hard to appreciate, unless you’ve had to write big programs in languages like BASIC, which had no such protections. (Micro-managing every name in every program, to make sure that Routine #501 didn’t step on the variables that routine #1223 was using… Be glad that Python does most of that for you.)
Unfortunately, namespaces are usually created implicitly: e.g., when you import a module or package, or call a function. But we end up having to do a lot of both, so understanding when names are looked up, and which namespace(s) are consulted during lookup, can clarify a lot of things. There is a simple regularity to how Python does it. The main complication is how many
Procedural matters (loops, branches, and functions) form yet a third pillar.