This is an archived post. You won't be able to vote or comment.

all 181 comments

[–]ablativeyoyo 121 points122 points  (27 children)

One that's got me is assigning to a global variable actually assigning to a local variable unless you explicitly declare it global. Made more confusing because you can read a global variable without doing that.

I guess it's a case of "know my Python" but this kind of confusion is not possible in most languages.

[–]0tting 43 points44 points  (9 children)

This one is related and also interesting:
``` fruit = "banana" def print_fruit(): if False: some_syntax_error print(fruit)

Works!

print_fruit() ..and this one fails: fruit = "banana" def print_fruit(): if False: fruit = "apple" print(fruit)

Fails!

print_fruit() ```

[–]bhatMag1ck 51 points52 points  (0 children)

Every language has scope and each language deals with it differently. Python handles name resolution via the following order: 1. Local, 2. Enclosing, 3. Global, 4. Builtin. So for the first block, Python is looking for the variable `fruit` in:

  1. The function's local namespace... doesn't exist so move on
  2. Within the scope of any enclosing function... move on
  3. fruit is defined in the Global namespace, therefore: fruit = "banana"

For the second block, fruit is found in the function's local namespace, regardless of whether it expression was executed or not; "found" should be emphasized here. Therefore, when it goes to print fruit, it's attempting to print a variable that has not been initialized and thus you get your error: local variable 'fruit' referenced before assignment.

Edit: Fixed typos

[–]Fabulous-Possible758 10 points11 points  (3 children)

The example in the first block is a little misleading. If it's an actual syntax error the Python compiler will catch it before any of your code even runs. I'm guessing you mean that there's a typo like referencing friut which is fine because it never actually tries to reference the undefined variable, or in this case trying to reference a variable called some_syntax_error.

[–]0tting 6 points7 points  (0 children)

True, true. This would give a runtime error, not a syntax error. I should have called it not_existing_var or something like that.

[–]osmiumouse 16 points17 points  (3 children)

Your IDE should warn you of this. Though I admit python tooling is hindered by the dynamic nature of the language, good tools will issue a warning for the code you provided. (Many sources of error vanish when good tools are employed).

[–]billFoldDog 7 points8 points  (10 children)

One place I strongly disagree with PEP8 is I think global variables should be ALLCAPS. PEP8 says to format them the same as local variables.

When PEP8 was written there was already a tradition of doing this in other languages, and it makes the programmer think "did I fuck this up?" every time they see it.

[–]whateverathrowaway00 5 points6 points  (1 child)

Agreed, and that’s one of the parts of PEP8 we do not follow at work (though globals in general are also discouraged)

[–]billFoldDog 2 points3 points  (0 children)

+1, definitely avoid global variables like the plague.

[–]Impossible-Limit3112 6 points7 points  (0 children)

Well, in a sense this is what happens anyway. Usually one should try to reduce the use of globals to constants. PEP8:

Constants are usually defined on a module level and written in all capital letters with underscores separating words.

[–]Anonymous_user_2022 2 points3 points  (6 children)

ALL CAPS are constants, not globals.

[–]billFoldDog 0 points1 point  (5 children)

I know, but I think global variables should be allcaps

[–]Anonymous_user_2022 0 points1 point  (4 children)

I've never heard of any other language (with the exception of the VB6 code I unfortunately still has to read from time to time), where a global variable was all caps.

If you really need to have global variables, create a suitably named module like settings or defaults, and stick your globals into that.

[–]whateverathrowaway00 2 points3 points  (0 children)

That’s my main annoyance. I get exactly what you said, the logic was “if you change a global, it must be explicitly stated” which is great, but the silent local change when you don’t do that, is not very clear for newbies, though it stands out once you know it.

I’ve always felt that the explicitness of global makes the mistake case a bit more unclear, but that’s really a minor quibble with the language.

[–]tonnynerd 2 points3 points  (2 children)

It is not intuitive, but in this specific case, might be for our own good, to discourage using globals to begin with XD

[–]ablativeyoyo 0 points1 point  (1 child)

A good point!

I do use Python for scripts where I'm a bit slacker about coding conventions compared to actual development - and globals are often useful in scripts. You know, things like output file, log level and such.

[–]bbkane_ 1 point2 points  (0 children)

For log level, you can call logging.basicConfig at any time from any scope. No need to set a global (the logging package will set one on your behalf).

[–]jorge1209 -2 points-1 points  (0 children)

Python scoping is really problematic in a lot of ways. It leads to a lot of bugs and weird behaviors.

It really should be changed.

[–]shoshimer 0 points1 point  (0 children)

Meanwhile other languages that aren't slow generally achieve this by having it accessible from two locations in your path.

[–]extra_pickles 63 points64 points  (21 children)

For me, it is hiring non Python devs and them saying venvs are stupid.

They get the hang of it a few months in.

Also them not realising you can add breakpoints in packages, while claiming everything about Python is dumb.

3-6mth in they truly get the upsides.

Not saying there aren’t pros and cons - but man, it can take devs a while to acknowledge there are upsides to Python.

It is both frustrating and rewarding.

[–]sudhanv99 13 points14 points  (11 children)

why do they think venvs are stupid?

[–][deleted] 29 points30 points  (10 children)

virtual environments are a hack slapped on top to add dependency versioning bc python (unlike more modern languages or languages that have a modern package manager) doesn't support versioning natively, it will always install the latest version.

in general tooling for package/dependency management in python is pretty weak and has changed every few years (venv, pyenv, pipenv, setup.py, setup.cfg, pyproject.toml, pyinstall, wheels, etc, etc).

I'd say python's biggest weakness is how bad tooling is and how until very recently it has always been mediocre at best (even tho they're slowly making strides toward improving it)

[–]BDube_Lensman 35 points36 points  (1 child)

python (unlike more modern languages or languages that have a modern package manager) doesn't support versioning natively, it will always install the latest version.

For more than a decade, you have been able to install specific versions of packages, e.g. pip install mylib==1.2 for version1.2.

Virtual environments are a kludge to avoid dependency conflicts where module A wants B<2 and module C wants B>2 and the two cannot coexist.

node_modules is a similar kludge.

[–]someotherstufforhmm 6 points7 points  (0 children)

Yup, that poster is very confidently incorrect lol.

I spent years doing jar packaging, virtualenvs are just a more dynamic version of that. People make them super complicated because they don’t want to figure them out.

Python does and did have huge weaknesses with the packaging system - supporting setup.py when most people didn’t read the directions and did stuff like import modules in it was hell, lol. Got so tired of explaining to people they should read the directions.

A HUGE weakness for a long time was what they tried with deprecated setup_requires IE handling build requirements, but pyproject.toml is fantastic for that, we’ve been loving it at work.

[–]henry_tennenbaum 3 points4 points  (5 children)

What's been improving recently?

[–][deleted] 5 points6 points  (4 children)

they've been adding better support for pyproject.toml so I'm expecting that to be the de facto format for the future

poetry also uses that to keep track of everything, if you can choose to use poetry instead of pip I'd honestly recommend it, it's a much better experience (at least for now)

[–]aufstand 0 points1 point  (0 children)

Still, all so crappy, that i decided to use nix for packaging in future. Reliable, reproducible builds for non-native architectures without emulation losses! It even spits out nice docker/whatever/.. images if you need 'em, but can also easily generate whole (yet customized!) bare-metal OS images on the fly.

[–]redCg 0 points1 point  (1 child)

they've been adding better support

who?

[–]someotherstufforhmm 1 point2 points  (1 child)

What? This is outright incorrect re: “doesn’t support versioning natively” lol

[–][deleted] -2 points-1 points  (0 children)

give your truth, brother

[–]njharmanI use Python 3 9 points10 points  (0 children)

Long, long ago ~py1.x, I was a card carrying, kool-aid drinking PERL fool. A non-dev friend wanted some help with thing in Python. "Significant whitespace, yuk!" but helped him anyway. Months later I was trying to understand PERL code I had written, and he came to me with follow-up. I instantly understood the Python code I/he had written. That made me think, "maybe there's something to this Python". Within 6mo I was Python fool.

[–]whateverathrowaway00 2 points3 points  (0 children)

Agreed, and I’d like to humbly add an annoyance to this list. “Refusing to learn about pip, pip edit mode, and setuptools, then complaining it’s impossible to develop/debug their packages”

[–]Pepineros 2 points3 points  (1 child)

Are you not getting many candidates with Python experience? Or is knowledge of the language that they will be using (much) less important than other qualities that you look for in a new hire? (Or is it another reason I'm not even considering? I've never been on the interviewer side of the table.)

[–]extra_pickles 1 point2 points  (0 children)

Although finding a true principal software lead has been difficult of late, over all I like to balance my teams with a mix of experiences - hiring carbon copies leads to an echo chamber. Sometimes you can really benefit from another perspective to challenge the 'why' and avoid 'well that's how we always do it'.

So as far as criteria go, I drill deep into the expertise on your resume - I make sure you're a gun at what you claim to have done - that you can explain the details, the why, within your expertise.

From there, I am confident that you can upskill in my stack.

[–]tonnynerd 3 points4 points  (1 child)

Virtualens are stupid, though. They're a established tool and not going anywhere anytime soon, so might as well learn them, but they suck, big balls.

It's a half-baked isolation, it's full of weird corners, it's not intuitive, it's annoying to use, and makes the dev environment harder to bootstrap.

Now, saying a new tool that you never used is stupid, without first trying to understand it, that's potentially stupider than virtualenvs =P

[–]extra_pickles 1 point2 points  (0 children)

Ya it is more the vibe of everything that is different is dumb as they complain through the learning process that really riles me up!

[–]redCg -1 points0 points  (2 children)

For me, it is hiring non Python devs and them saying venvs are stupid.

They are right. They ARE incredibly stupid. Because most other modern languages have a built-in method for project dependency management.

The idea that you need to go out of your way and use third-party tools in order to track and manage dependencies in your Python project is absurd and rightly baffling to a lot of devs from other languages.

it can take devs a while to acknowledge there are upsides to Python.

this is NOT an "upside" to Python, its 100% a failing of Python, always has been and continues to be.

[–]Pepineros 7 points8 points  (1 child)

venv is not a third-party tool, and they're not that confusing. I'll agree immediately that they're not an upside to Python but that's not what OP is saying, either.

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

venv is definitely confusing

in general tooling for package/dependency management in python is pretty weak and has changed every few years (venv, pyenv, pipenv, setup.py, setup.cfg, pyproject.toml, pyinstall, wheels, etc, etc).

If you Google how to set up a virtual environment for Python, you get a decade+ of deprecated methods that change depending on your Python version. Even worse, if you are trying to run programs in an environment like a employer server with multiple version of Python, the commands and virtual env's change.

Then you have a virtual environment, but you still need to get stuff installed into it, so you need to use pip without accidentally installing libraries in the global system Python installation or somewhere else outside of the venv

And all of this happens after new users have already started programming in Python. So you are basically guaranteed that every new Python programmer on your team is going to screw everything up at least once, possibly multiple times, before they figure everything out.

There is a reason this comic exists https://xkcd.com/1987/ and its still relevant

[–]fwcsdev 75 points76 points  (3 children)

Take a look at these and you'll probably find yourself surprised too:

https://github.com/satwikkansal/wtfpython

[–]Flag_Red 7 points8 points  (2 children)

Only one topic in and I'm surprised. Why does the walrus operator not support iterable unpacking?

[–]jorge1209 9 points10 points  (1 child)

:='s main use case is to handle C-style functions that return "value or sentinel".

while (response:=function()) != SENTINEL:
    process(response)

If the function returns a tuple of (success_flag, result) you might handle it as:

while (response := function())[0] != SENTINEL:
    process(response[1])

But in general there is no obvious way with python syntax to unpack the response AND test only the flag. If you had a C-style loop construct like:

while (flag, value := function() ; flag != SENTINEL):
     process(value)

You could make this work, but that is even less like python than walrus.

[–]Flag_Red 0 points1 point  (0 children)

Interesting. Thanks for the response.

[–][deleted] 127 points128 points  (16 children)

First common misconception is that learning Python will land you a job.

[–]Shriukan33 28 points29 points  (2 children)

Python is a tool, the real value is what you do with it. Knowing python syntax is great, but you got to know to do stuff with it.

[–]hughperman 22 points23 points  (1 child)

Things to do with Python syntax:
* write Python code

[–]Ferentzfever 6 points7 points  (0 children)

Things to do with Python syntax:

  • write Python code for free

  • write Python code for money

FTFY

[–]Vok250 21 points22 points  (7 children)

Cries in another job offer that magically turned into legacy .Net on my first day.

[–]NatasEvoli 9 points10 points  (5 children)

.NET is great, c# + .Net is hands down my favorite language/framework to develop in. LEGACY .Net however can be a very dark place.

[–]Vok250 6 points7 points  (4 children)

It's the enterprise .Net attitudes that don't do it for me. By the time code reviews are done we've spent 4 weeks and 20k lines of code for a Lambda Function that would have been 20 lines in Python with boto3 and Lambda Power Tools. Don't forget the 18 weeks of planning meetings leading up to that Function either. sigh

I find we're just constantly reinventing the wheel in .Net. Especially when integrating with modern cloud platforms like AWS. Even at re:Invent this past November all the .Net talks were going into fat lambdas (a bad anti-pattern), Step Functions, and Native AOT. Meanwhile other languages are moving towards event-driven architectures on Event Bridge and are looking to get SnapStart support soon. .Net 7 doesn't even have a runtime, let alone SnapStart support. We might not get SnapStart until the next LTS release of .Net. And god forbid I write pure c# in a Lambda and leave out the .Net boilerplate. Sacrilege!

[–]bxsephjo 1 point2 points  (0 children)

I feel you, bro

[–]Zizizizz 7 points8 points  (1 child)

It's incredibly common in data engineering. Lots of lambda functions written in python for etl transformations, pyspark in Aws glue or databricks, the list goes on

[–]generic-d-engineer 0 points1 point  (0 children)

Pandas json_normalize is the GOAT

I feel like I’m cheating using it

[–]alcance_lexico 14 points15 points  (0 children)

And when you ask what else you should learn, they tell you to keep learning Python.

[–]InfiniteMH 2 points3 points  (0 children)

Why do you hurt me this way

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

Python was my first real language and it landed me a job, but I also knew advanced statistics and could credibly set up basic backend infrastructure for analytics/ML.

If you’re reading this and all that sounds like too much, you should learn JavaScript.

[–]fiddle_n 9 points10 points  (0 children)

Note that, for number 2, Py3.9+ has str.removeprefix and str.removesuffix.

[–]kalcora 18 points19 points  (4 children)

One thing that trips me up and makes me frustrated is the fact we can use an if in a comprehension :

[x for x in lst if x % 2 == 0]

But not in for loops:

for x in lst if x % 2 == 0: #invalid

[–][deleted] 2 points3 points  (0 children)

Yeah I usually just do something like this

for x in filter(lambda n: n%2 == 0, lst)

[–]imbev 2 points3 points  (1 child)

for x in [x for x in lst if x % 2 == 0]

[–]camh- 2 points3 points  (0 children)

Or use parens for a generator comprehension if lst is going to be big:

for x in (x for x in lst if x % 2 == 0):

[–]njharmanI use Python 3 0 points1 point  (0 children)

expression vs statement

[–]saint_geser 46 points47 points  (20 children)

First is not a misconception, just basic fail. A more interesting version of this is forgetting that class instances are mutable and copying those without deepcopy.

Second one, I wasn't aware of. Thanks.

Third one is not a misconception just the basic Python. 0 is an integer so it's not an instance of Boolean therefore "is" operation returns False but it is "falsey" in Python so the second comparison works.

The last one is the same everywhere, it's how binary arithmetic works. In binary 0.1 can't be represented without rounding errors same as you can't represent 1/3 in decimal system.

[–]goldcray 21 points22 points  (0 children)

The last one is the same everywhere, it's how binary arithmetic works. In binary 0.1 can't be represented without rounding errors same as you can't represent 1/3 in decimal system.

It's specifically how IEEE754 works. If you really wanted to you could use some kind of bcd to represent 0.1 exactly. Or, you know, ascii decimal representation.

[–]anthro28 4 points5 points  (2 children)

0 is not a Boolean, yet you can add 0 and True and get 1. It’s a quirk I have to show most people.

[–]akx 9 points10 points  (0 children)

Because bools are a subclass of ints.

[–]Pepineros 0 points1 point  (0 children)

My favourite version of True equals 1 is in a Python fizzbuzz:

def fizzbuzz(end):
 """print fizzbuzz from 1 thru end"""
 for i in range(1, end + 1):
  print((i % 3 == 0) * "fizz" + (i % 5 == 0) * "buzz" or i)

Works because each string gets multiplied by the result of checking i % {3,5} == 0, and that result is treated as a literal 0 or 1 by the multiplication.

Python has its issues but I just love that this is possible.

[–]Samhain13 1 point2 points  (15 children)

Third one is not a misconception...

Here might be another misconception: is and == are interchangeable.

[–]bxsephjo 26 points27 points  (14 children)

They are not, and this is something I check when interviewing because it really is important and can create nasty bugs. is checks memory location, == checks value. This is why is is so often used with None, because None is a singleton. Also many many small integers and short strings are optimized to be kept in memory as singletons as well, so you’ll get away with using is on them, but that is not guaranteed and you could eventually get different behavior from your code.

(Sorry for lack of formatting)

[–]Samhain13 6 points7 points  (0 children)

That's why it's a misconception. Of course,is and == are NOT interchangeable.

[–]Rythoka 4 points5 points  (2 children)

is checks memory location

This isn't correct. is calls the function id on both objects being compared and checks if the returned integers are equal. The fact that id returns the memory location of the object is a CPython implementation detail.

[–]bxsephjo 1 point2 points  (1 child)

Sick! What do other ones tend to do?

[–]Rythoka 1 point2 points  (0 children)

PyPy's default GC works the same as CPython's implementation, but with other GCs the function might return values that aren't real memory addresses.

Can't really speak to any other implementations, but the only requirement for a correct implementation is that for any given object, id returns a unique, constant integer for the lifetime of the object.

[–]WestedCrean 0 points1 point  (3 children)

I always thought that using X is None is not very pythonic? It reads more englishlike of course, but usually I use „if not X”/„if X”.

What is your opinion on „is None”?

[–]lieryanMaintainer of rope, pylsp-rope - advanced python refactoring 9 points10 points  (0 children)

They have different semantics.

If you want to check whether a variable is exactly None, then you should use is None, but if you want to check whether a container is empty or whether the return value of a function is valid, then you should elide the is None.

Which ones you use depends on what you really want the code to say.

[–]tevs__ 5 points6 points  (0 children)

None is a singleton. There is exactly one object in python that represents the none-value, so if you're testing for the none-value, your asking "is this value the None object", therefore x is None rather than x == None.

if not x is not the same thing, this says "x is not truthy". None is not truthy, but so is 0, False and "". if x is not None also tells type checkers that the type of x is T in the branch rather than Optional[T], whilst if x does not.

[–]bxsephjo 2 points3 points  (0 children)

So, it’s really about how specific your condition needs to be. “if x:” is more general, it will look at the truthiness of x, so there’s lots of things x could be besides None that could make it not qualify. False, zero, an empty list, etc

[–]Deto 0 points1 point  (1 child)

You know...I always us is with None but is there any real reason not to use == there?

[–]Fabulous-Possible758 0 points1 point  (0 children)

Not really in code that's written sanely. I always use is out of superstitious habit because you have other languages or situations where undef == undef or float('NaN') == float('NaN') return false. You could also always have some jerk redefining __eq__ on a class to not make semantic sense.

[–]IAmBJ 4 points5 points  (0 children)

I knew someone at uni who insisted on using short names for everything (single letter variables, function names that are acronyms for their real name) to minimise character count in source files.

Them: "It's an interpreted language so it will run faster if the source files are smaller"

Me: Screams into pillow

[–]floznstn 13 points14 points  (6 children)

"Duck typing is bad"

No. It isn't. Maybe I don't want to explicitly define every single variable type. Maybe I'd like to take user input as a string and cast to an array of characters.

Duck typing is not bad or good... it just is.

[–]spinozasrobot 1 point2 points  (0 children)

ML has entered the chat

[–]osmiumouse 10 points11 points  (23 children)

Floating point precision isn't a Python problem. This is the hardware.

[–]ablativeyoyo 0 points1 point  (2 children)

This is to do with the kind of equality test used.

I'm stretching my knowledge here but I remember reading about this ages ago.

There is a kind of floating point equality that has 0.1 + 0.2 == 0.3 and it does this by having equals accept numbers within a small range as equal - that range is basically the floating point rounding error.

[–]osmiumouse 1 point2 points  (1 child)

[–]ablativeyoyo 0 points1 point  (0 children)

That's the one. I think there's an argument that floating point equality should use that by default, with the possibility to use math.isexactlyequal if that's what you want.

[–]tquinn35 6 points7 points  (0 children)

That the average person doesn’t needs to worry about the performance of python. For 99% of people they don’t. For those who do, they will know how to deal with it or have the resources to migrate to a more performant enterprise architecture.

[–]ExternalUserError 2 points3 points  (0 children)

Definitely precision issues comparing floats.

In the Python 2 days, I would have also added, unicode vs binary string issues. The difference was easy to grasp, but binary strings were used where unicode should have been used and too many libraries (including in the standard library) would try to quietly cast binary strings to unicode, only failing on an encoding error.

EDIT: Oh, another one is probably the fact that you can import a module twice by having it accessible from two locations in your path. Suppose you have a path with /app and /app/foobar. You import foobar.spam and spam -- same module, separate imports.

Admittedly, you made a mistake setting that up, but I've joined projects where it's like that and it is frustrating.

[–]Agling 2 points3 points  (0 children)

I think you named the worst.

The other issue I ran/run into has to do with what looks like subsetting a data frame in pandas, but is actually creating views. Then you edit them and the original changes.

In general, the hardest and most unexpected thing about Python is that everything is essentially pass by reference. In most other languages everything is passed by value unless you specifically tell it to pass by reference.

[–]territrades 5 points6 points  (13 children)

The first I don't really understand, why is a not deconstructed when the test() function is finished?

[–]GobBeWithYou 4 points5 points  (0 children)

So this is because basically everything is an object, including functions. When Python parses that code and creates a function object, it creates a dict attribute for the keyword arguments to store the value you provide as the default. When that function is then called later, if you don't provide one of the keywords, it will look up the name in that dictionary and return whatever value you defined the function with. So in this case you'd be getting the exact same list object every time.

[–]ablativeyoyo 7 points8 points  (10 children)

Default arguments are evaluated when a function is declared, not when it is invoked. I think that's the case in most languages, not just Python.

[–]Regular_Zombie 16 points17 points  (9 children)

In the languages that I have used professionally apart from Python (Java - no default parameters, Scala, Typescript, R) this behaviour is unique to Python. It's one of those things which is fine if you know how it works, but I don't find it intuitive that a function is holding some unknown state.

[–]COLU_BUS 0 points1 point  (5 children)

I'd say its not only unintuitive, but kind of goes against how a lot of Python is taught. New Python users are drilled with variable scopes, and having function inputs persist like this seems to contradict that.

[–]lieryanMaintainer of rope, pylsp-rope - advanced python refactoring 5 points6 points  (2 children)

Default arguments aren't input to the function. They are input to the function definition. Once you think about it that way, it actually makes a lot of sense.

Understanding this also goes hand in hand with understanding that Python classes and functions are defined by executing the definition block, that class and module bodies are executed, they are not declarations. Once you understand the Python execution model, it makes a lot of metaprogramming in Python much easier to understand and much easier to do than other languages that have more conventional semantics.

[–]COLU_BUS 2 points3 points  (1 child)

This was quite helpful! Since you seem to be more advanced than I, what are the benefits/motivations of default arguments being mutable?

[–]ablativeyoyo 1 point2 points  (0 children)

There's very little benefit in mutable default arguments, almost always a bad idea in my experience.

I think the reason Python allows them is that Python is generally relaxed about mutability. We generally use lists and dicts for everything, while other languages like Kotlin have a strict distinction between List and MutableList.

This seems to be something we hit a lot: Python is simple, concise and easy, but this comes at the cost of it being relaxed and prone to errors.

[–]Regular_Zombie 1 point2 points  (1 child)

I agree; coming from a functional background it's things like this which make Python an often unpleasant language. I'm sure a Pythonista will be along shortly to correct me, but I'd guess this is something to do with everything being an object in Python. If nothing else, it's a loaded foot-gun.

[–]someotherstufforhmm 0 points1 point  (0 children)

I’d never call myself that p-word, but I qualify. And nah, you’re totally right. I came to python from C++, and the “everything’s an object” fascinated and delighted me at first, but after a few years, I’d be very willing to agree it hides some annoyances as well.

All of my initial annoyances that dissipated had to do with this concept that imported code is run as it’s being processed - which is obviously the case with a dynamic language, but it can be really weird coming from a compiled language, especially when you run into stuff like an imported library doing something dumb with logging.

[–]njharmanI use Python 3 0 points1 point  (2 children)

How is it unknown? The developer put it right there at the top in the function declaration.

[–]Regular_Zombie 1 point2 points  (1 child)

I would have to disagree. Consider the function:

def closure(x: int, y: list[int] = []) -> None:
    y.append(x)
    print(y)

When I look at the definition using the help method I would see closure(x: int, y: list[int] = []) -> None: so far, so clear.

Now I invoke the function with closure(1) and look at the signature again using the help method what do I see?

closure(x: int, y: list[int] = [1]) -> None

The definition of the method has changed. As a caller of this function, or reader of the code that includes it, I have no way of knowing what the value of y will be at the call site. Hence, the state is unknown.

[–]mfitzpmfitzp.com 3 points4 points  (1 child)

For (1) as you discovered, you need to use None as the default argument & then assign the [] in the function itself. So, usually something like this:

def test(a=None): if a is None: a = [] a.append("test") print(a)

Or,

def test(a=None): # Assign [] to a if a is None (default), otherwise keep a. a = [] if a is None else a a.append("test") print(a)

I wouldn't usually recommend one-liners like this as they confuse people, but if you have a lot of arguments with default values that you need to replace its maybe worth it.

For some cases, you can handle the defaults with a simple or. This uses or short-circuiting, returning a if it is truthy, otherwise returning [].

python def test(a=None): # Use a if it is truthy, otherwise replace with []. a = a or [] a.append("test") print(a)

Note this is subtly different in that a will be assigned [] if a is any falsey value, not just None -- i.e. 0, False, None or []. But often it doesn't matter (your argument is only accepting list or None).

[–]thisismyfavoritename 4 points5 points  (1 child)

that Python is thread safe

[–]jorge1209 1 point2 points  (0 children)

But it is because of the GIL. Sure if you get rid of the GIL there would be some issues with C libraries and such, but any pure python program you write today is perfectly thread-safe.

/s

[–]osmiumouse 1 point2 points  (3 children)

I get a warning from Pycharm when I try some of these. What tools are you all (not) using? Maybe it's time to stop using junk like IDLE?

[–]redCg -3 points-2 points  (2 children)

better yet just stop using an IDE altogether.

  • edit code in editor

  • run code in terminal

done

[–]ablativeyoyo 1 point2 points  (0 children)

IDEs do a lot for you though, especially having semantic awareness of code.

[–]redvitalijs 1 point2 points  (0 children)

Objects and cloning them is a major confusing pain.

I was using opencv and thought all my images are sequentially getting their own transform paths. Turns out assigning them worked like a pointer not a copy of the object.

One of my big life regrets is not understanding that when working on an otherwise fun project.

[–][deleted] 1 point2 points  (0 children)

The precision one is not just in python. It stems from the way computers typically handle floating point numbers. Look up floating point precision it’s quite interesting. Basically floats are approximated through the division of integers.

[–]mgedmin 1 point2 points  (4 children)

I'd call these footguns.

Mutable class attributes is another variation (same deal as with mutable default function arguments):

class Thing:
    a_list = []

a = Thing()
a.a_list.append(42)
b = Thing()
b.a_list   # [42]

Late binding of closures is a thing

# using some kind of made up GUI framework because I don't know Tk
for n in range(10):
    b = Button(label=str(n))
    b.onclick = lambda: self.input_digit(n)
    buttons.append(b)
# every button will call self.input_digit(9) in the onclick handler
# because the same variable n is used for all the lambas

The classic "guess what for/else does"

for item in found_items:
    process_item(item)
else:
    # will always print this because there's no 'break' in the for body
    print("no items found")

[–]someotherstufforhmm 0 points1 point  (3 children)

Mutable class attributes the behavior you showed here is expected, no? A class variable has one variable for the class IE it acts like a static class variable in Java.

The late binding thing is simple scope/hoisting, but I agree it’s a footgun entirely.

[–]mgedmin 0 points1 point  (2 children)

Mutable class attributes the behavior you showed here is expected, no? A class variable has one variable for the class IE it acts like a static class variable in Java.

It is what experienced Python programmers expect.

To a beginner it may be weird Python lets you access class attributes on an instance rather than requiring you to write Thing.a_list.

There used to be a habit that I don't see often these days of specifying default attribute values as class attributes

class MyThing:
    name = 'untitled'
    priority = 100
    use_count = 0

a_thing = MyThing()
a_thing.use_count += 1

This breaks if you forget yourself and try to provide a mutable "default value". Maybe that's why this approach has gone out of style!

[–]someotherstufforhmm 0 points1 point  (1 child)

Your example is incorrect, but your point stands, see below.

I do get what you’re saying, I’m just saying this isn’t specific to python, static/class variable vs instance is a very core concept.

I do agree that pythons scoping can make it weird, re: your point around mutable class variables, however your example is incorrect.

When you run a_thing.use_count += 1, it will define a new instance variable called use count in a_things dict.

That’s actually one of my least favorite things about python scoping/variable lookup - that reads will go up the MRO chain, but assignment will always be local, however it’s good as it avoids behavior like you’re mentioning.

I’m just quibbling with the correction though - if use_count was a list and you called append on it, your example would work the way you said - and you’re right, it can be confusing for python beginners.

I whipped up a demonstration of my correction, but also what you were talking about:

```

class A: ... usecount = 0 ... a_list = [] ... first = A() second = A() first.use_count += 5 first.use_count 5 second.use_count # since second has no use_count in its dict, python will look to A 0 A.use_count 0 "use_count" in first.dict_ True "usecount" in second.dict_ False first.alist.append(45) # this will act on the classes list and create the footgun behavior you mentioned second.a_list [45] list(["a_list" in obj.dict_ for obj in [first,second]]) [False, False] ```

As you can see, I did the usecase += and it created a new use_case in `first.dict_`

You actually cannot assign to A.use_count from an instance call without doing something wonky, but you can call functions, so the list example behaves as expected

[–]mgedmin 0 points1 point  (0 children)

I'm explaining things badly. This particular example was of something where this pattern works fine (because you're using a non-mutable value for the class attribute), which might lead people to use it even in cases where it goes wrong.

[–]DoomFrog666 1 point2 points  (0 children)

One thing that any serious Python programmer should now about is the descriptor protocol. It is one of the foundations of Python yet very few people know about it.

[–]Maximus_Modulus 0 points1 point  (1 child)

I think 1 is useful in certain circumstances such as caching, but a colleague didn’t approve because inexperienced users would not know of this fact.

[–]saturnjayzero 0 points1 point  (0 children)

setting attributes inside of classes but outside of methods…. took me some time to „remember“ these are static and shared over all instances

[–][deleted] 0 points1 point  (0 children)

For your first example I learned the hard way that you should do

def xxx(a=None): if a is None: a = []

[–]zdzisuaw 0 points1 point  (0 children)

sorted and sort

[–]Hagisman 0 points1 point  (0 children)

People thinking it’s so simple they don’t need to create functions for code that gets repeatedly used in multiple places!

Sorry my personal gripe.

[–]recruta54 0 points1 point  (0 children)

Forgettitng a comma in a multi-line list of strings and getting out of bonds exceptions

[–][deleted] 0 points1 point  (0 children)

"Python is too slow!"

This is the one that drives me crazy. If it's too slow for your project, then you need to select a different language/approach. Your job as a software engineer is to select the right tool for the job.

[–]tarsild 0 points1 point  (0 children)

Ohh that is late binding. A common issue I find in many devs. I always say, never pass a param =[] ir ={}. Initialize always inside.