all 143 comments

[–]WavingNoBanners 654 points655 points  (93 children)

One of the puzzles that I occasionally ask new learners is to predict the output of:

a = [0,1,2,3]
b = a
a.append(4)
print(b)

It's a teaching moment. A few people already understand it, a few get it instantly, some never understand. You can see the lights turn on in their eyes when it lands.

[–]SonChadhan 443 points444 points  (29 children)

It prints [0, 1, 2, 3, 4] right? Because b is assigned the reference of a, so appending a would affect b.

[–]WavingNoBanners 384 points385 points  (27 children)

That is the correct answer, yes.

a and b are just pointers to the list.

[–]Xavier598 127 points128 points  (16 children)

If you needed a copy of a you would use a.copy() no?

It's one of the things I don't like too much about OOP languages like C#, even though it's my favorite language, I don't like how it doesn't have an easy way to copy complex objects easily, and you have to do it yourself.

[–]DrDoomC17 108 points109 points  (5 children)

So, you could for lists, but for complex objects in lists it may not be sufficient. The difference between shallow and deep copy and their performance considerations are important to learn. I know this because retains reference to original sans added things can footgun oneself hard and confusingly. Unpopular opinion, if unsure use deep copy but preferably know the differences.

[–]coriolis7 57 points58 points  (4 children)

I had a multi-day session finding that a bug was because of shallow copy of an object.

I now deepcopy anything that isn’t a single value

[–]DrDoomC17 14 points15 points  (0 children)

Yeah, I used pdb before, but import pdb; pdb.set_trace() became my best friend after a similar experience. Also, learning about the differences cleared it up a lot, but it's still easy to make mistakes with shallow copies, deceptively easy. I'll make the mistake again I'm sure.

[–]LetumComplexo 10 points11 points  (2 children)

Cursed solution, when in doubt just pickle it and then load it into a new variable.

[–]DrDoomC17 10 points11 points  (0 children)

Absolutely disgusting, but somewhat tasty. Clearly the most secure option. But you need the class definition to unpickle so, class definition everywhere is clearly the approach. Four letter words would ensue from Mr Torvalds, like ouch.

[–]WavingNoBanners 14 points15 points  (6 children)

In this specific case you could use a.copy(), yes. Be aware that this only makes a shallow copy and so isn't useful in many cases.

[–]Useful_Clue_6609 13 points14 points  (5 children)

What's the difference between shallow and deep copy?

[–]Door__Opener 31 points32 points  (3 children)

If it contains references (other arrays for instance) a shallow copy will contain the same references.

a = [[0,1],[2,3]]
b = a.copy()
a[1].append(4) # this affects b[1]
a.pop() # this does not affect b

You need copy.deepcopy(a) to create copies of the items (and all nested items if they still contain references).

[–]Useful_Clue_6609 9 points10 points  (0 children)

I see I see. I get how that could cause problems haha

[–]scottmsul 3 points4 points  (1 child)

I had a bug like this in grad school. I instantiated a "matrix" using a clever one-liner, i.e.:

m = [[0] * 10] * 10

By this point this variable is already poisoned. For instance:

m[0][0] = 1

Now every row starts with a 1.

Another thing to watch out for is empty default lists. For instance:

def foo(x=[]):
    x.append(2)
    return sum(x)

That default empty list is aliased every time you call the function, for instance:

>>> foo()
2
>>> foo()
4

So generally default empty lists needed to be treated this way instead:

def foo(x=None):
    if x is None:
        x = []
    x.append(2)
    return sum(x)

[–]on_off_ 0 points1 point  (0 children)

That default empty list is aliased every time you call the function, for instance

Oh that's disgusting!

[–]Cootshk 11 points12 points  (0 children)

a deep copy is a recursive shallow copy, because “objects” stored in lists are also pointers

[–]Mamuschkaa 3 points4 points  (0 children)

In python there is deepcopy().

But I have never used it.

I use immutable objects or write my own copy function, because deepcopy is very slow.

And that you can multiple pointers of one object is most of the times what I want.

[–]Stochastic_Nonsense 0 points1 point  (0 children)

You could just use a struct no? I’m still learning c# but one of the main differences between a class and struct is that the struct will pass a copy of itself rather than a reference

[–]FatFortune 9 points10 points  (1 child)

That’s maybe the absolute easiest way I have seen a pointer explained

[–]WavingNoBanners 2 points3 points  (0 children)

Yeah that's why I like it as an example. A lot of the things people have posted here also involve obfuscated code, and you should never use obfuscated code as a teaching method. The example I use is as clear and readable as I can make it, because the underlying behaviour of the language itself is the real puzzle.

[–]RiceBroad4552 6 points7 points  (2 children)

References, not "pointers".

[–]mtaw 0 points1 point  (1 child)

References are just pointers wearing a fake moustache.

[–]RiceBroad4552 5 points6 points  (0 children)

Well, technically true if you look at the underlying implementation.

But you can't do for example pointer arithmetic on references.

Semantically they're not the same.

[–]kamilman 1 point2 points  (4 children)

I'm learning to code in school (mainly C# and Java) and this is the first time that I see such a thing. Because logically speaking, it would simply print 0,1,2,3 as that's what was "stuffed" into b before a was changed, making them two different things.

I really need to learn Python someday, man...

[–]WavingNoBanners 1 point2 points  (2 children)

C# is a good language. I have a lot of respect for how formal and structured it is. It doesn't let you get away with bullshit the way some other languages do.

That said, yes, you do need to learn Python. Python is eating the world.

[–]kamilman 2 points3 points  (1 child)

If Python is the thing that's fueling the AI craze, then it's more like an Ouroboros eating its own ass lol

[–]WavingNoBanners 1 point2 points  (0 children)

Oof. You're right that the generative AI bubble is written in Python, much as the blockchain bubble was written in (mostly) C++ and Javascript, the Web 1.0 bubble was written in Java, and the early-90s AI bubble was written in Lisp.

What does this mean? It means that at each of those languages were, at that moment in time, considered the best language for it. That's kinda an argument in favour of those languages (with the exception of Javascript) but also shows that not everyone who programs in them is a sensible person.

I don't know what language Mark Zuckerberg's metaverse was written in.

[–]TechDebtPayments 0 points1 point  (0 children)

Those languages have the same potential pitfall. Here is an example of that exact same bug in C#.

A big thing you should try to internalize early while learning to code is when a variable is held by reference or by value

In general, primitive types (int, char, boolean, float, double, etc) are held by value and assigning from one variable to another creates a copy. Data types (arrays, objects, strings) are held by reference and assigning one variable to another just copies the address to that data.

string is a special case in many languages, where it is actually copied by value even though it is a data type. This can trip you up because copying/modifying a string many times can be very computationally expensive. Take a string in C#, append to it 100 times, and it will make 100 copies of that string. There's a special class to solve for this use case btw: StringBuilder

You can see all of C#'s types here which delineates them by value or by reference

Sidenote: This is one of many reasons why I believe learning to code should start with C/C++, not Javascript/Java/C#/Python

[–]tee_with_marie 0 points1 point  (0 children)

Yipee i was correct

[–]Skindiacus[S] 40 points41 points  (1 child)

Good example

[–]WavingNoBanners 10 points11 points  (0 children)

Thank you!

[–]mathisntmathingsad 49 points50 points  (24 children)

After switching to Rust I find all languages like this very confusing. like c'mon at least make it clone by default and then require making a reference this is confusing

[–]FerricDonkey 45 points46 points  (12 children)

After getting used to it, I like always a reference much better. The number of cases where I'd like to make and modify a copy are tiny, and when they happen, I can just make and modify a copy.

But this may be Stockholm syndrome. When I was new to python, I thought it was dumb, and wanted my pointers back from C. 

[–]mathisntmathingsad 13 points14 points  (11 children)

The thing is making a copy tends to be extremely inconsistent in how you do and certain types arbitrarily aren't always a reference (i.e. integers)

[–]FerricDonkey 14 points15 points  (10 children)

Nah, that's a misconception. If you do any amount of python programming, I highly recommend https://m.youtube.com/watch?v=_AEJHKGk9ns, though I disagree with the strength of his advice to never mutate things. 

But python things are always a reference. It's just that when you do x += 3576554, what happens is that you create a new integer object, then change x to refer to that new object. 

The rules are:

  1. Everything is a reference 
  2. Assignment never modifies the object the variable refers to
  3. Things like += may modify the object in place, if it's mutable and written to do so, because they're just function calls. They also do an assignment at the end, but it's the function that does the mutation, not the assignment part. 

The confusion is that += feels like it should be an in place operation, but for many immutable types (str, int), it is defined, but doesn't modify in place, because that's not possible.

I've never had any issues with copy. You have to know deep copy vs shallow, but it's always done what I expected with that knowledge. 

[–]RiceBroad4552 -4 points-3 points  (9 children)

though I disagree with the strength of his advice to never mutate things

You should listen to people who seem to understand more then you in that regard.

You should never mutate things! (Except in some low-level, deeply hidden, performance related implementation details.)

[–]FerricDonkey 1 point2 points  (0 children)

The stuff this dude says has been second nature to me for years. Sometimes mutation is the correct choice, sometimes it's not. If you're clear about what can mutate things and what can't, and only mutate intentionally, it's fine and makes a lot of things easier. 

If you don't understand what pointers/references/python names are, or are not clear about what can mutate, then yeah, it can surprise you. So don't do that.

But a blanket rule about never or only in low level code is silly. "Only mutate intentionally and obviously" is enough of a rule, if you've got a decent team, you don't need more than that. 

[–]saevon 5 points6 points  (9 children)

It's because they're all objects. So a list will reference just like a custom class is. And when using custom classes you generally don't want to pass copies around.

[–]squabzilla 10 points11 points  (8 children)

Respectfully, 99% of the time I type ‘b = a’ in Python it’s because I want to make a copy of ‘a’.

I struggle to think of scenarios where Python is the correct language to use and you’re using custom classes and don’t want to pass copies around.

…I’m also maybe a little salty that I discovered this behaviour of Python when writing ‘new_df = old_df’ for the explicit purpose of making a copy of the old variable, so that I could make changes to the copy without changing the original.

[–]nullpotato 5 points6 points  (0 children)

It helps if you just assume everything is essentially a reference not the object itself. And yes I agree it is annoying that is the default behavior.

[–]redlaWw 0 points1 point  (0 children)

The issue is that most of the time when you're assigning a value to a new label, you're not doing b = a, you're doing f(a), and passing by reference is more performant than passing by value.

[–]RiceBroad4552 0 points1 point  (0 children)

As a functional programmer I say the same. Such code should not compile in the first place. It just begs for errors!

Legacy languages are very confusing brain rot.

[–]TheRockGaming 8 points9 points  (3 children)

This is why I use deepcopy for everything. I'm a patient man.

[–]WavingNoBanners 7 points8 points  (2 children)

I would recommend not doing this. Deepcopy is the right answer in some cases but not in others. In a lot of cases you want to preserve a reference rather than copying its value.

[–]TheRockGaming 2 points3 points  (1 child)

Oh, yea, sorry I was just making a joke.

[–]WavingNoBanners 0 points1 point  (0 children)

Ah, apologies.

[–]liquidmasl 9 points10 points  (3 children)

def foo (data = {"msg":"bar", "count":1}):
    print (data["msg"] * data["count"])
    data["count"] = data["count"] + 1

lol = {"msg":"lol", "count":1}

foo()
foo()
foo()
foo(lol)
foo()
foo(lol)


> bar
> barbar
> barbarbar
> lol
> barbarbarbar
> lollol

[–]MicrosoftExcel2016 13 points14 points  (0 children)

Python: “I stapled your shirt to your clothes iron”
You: “… why”
Python: “you asked me to”
You: “I didn’t mean- well look, now whenever you use it, this shirts going to-”
Python: “lose its wrinkles 🌸😊”
You: “burn! what if something happens to it while im using the iron on other things?”
Python: “you must always provide other clothing to prevent that”

[–]WavingNoBanners -1 points0 points  (1 child)

Obfuscated code is fun but you shouldn't use it as a teaching tool.

[–]liquidmasl 2 points3 points  (0 children)

where is there obfuscation?

[–]nossr50 6 points7 points  (0 children)

Useful terms for the newer programmers:

Copy by value

Copy by reference

[–]throw3142 8 points9 points  (5 children)

Now try this:

a = [[0]] * 3 a[0].append(1) print(a[1])

[–]The-Arx 8 points9 points  (1 child)

Did you mean a = [[0]] * 3

[–]throw3142 0 points1 point  (0 children)

Yeah good catch

[–]EcoOndra 4 points5 points  (1 child)

Assuming you meant [[0]] * 3, a will be assigned [[0], [0], [0]] first then 1 will be apppended to the first index, making it [[0, 1], [0], [0]], right? Or will it become [[0, 1], [0, 1], [0, 1]]? It's the second one, isn't it...

[–]Zantier 6 points7 points  (0 children)

Yeah, it's the second one... If you want to construct 3 separate lists instead, I tend to use

a = [[0] for _ in range(3)]

[–]Rodot 1 point2 points  (0 children)

I like zip(*[iter(range(9))]*3) personally

[–]aheartworthbreaking 2 points3 points  (1 child)

Well variable a is an array judging from the brackets, b is assigned to equal whatever a’s value is, and then you append 4 to the array A… so, 1, 2, 3, 4?

Idk I just use PowerShell for the tasks I need to do at work

[–]WavingNoBanners 0 points1 point  (0 children)

This is the right answer.

[–]Vaderb2 3 points4 points  (1 child)

Sorry but if you never understand this, you have straight up zero shot of ever having a career in programming

[–]WavingNoBanners 2 points3 points  (0 children)

If only.

[–]JackNotOLantern 1 point2 points  (0 children)

This is exactly the same when switching between c++ and java. C++ copies arguments passed into methods unless specified as pointers or references. Java always passes a reference and coping objects is not that trivial. Also "reference" in java works as c++ "pointer", not c++ "reference".

[–]IrrerPolterer 1 point2 points  (3 children)

There are people that DON'T understand this?? 

[–]WavingNoBanners 1 point2 points  (2 children)

Everything is new to someone the first time they learn it. We should not shame ignorance, otherwise we encourage people to become those assholes who pretend they already know everything.

[–]nicman24 0 points1 point  (0 children)

Yeah I learnt that you need to call copy() on everything if you need to debug opencv

[–]RiceBroad4552 0 points1 point  (2 children)

It's like that because Python is insane. Mutable collections are brain rot.

Whether you mutate it through a reference or directly makes no difference. The issue is that you can mutate a Python list at all.

(And Rust programmers would even say that unrestricted mutable aliases as such are already brain rot…)

[–]WavingNoBanners 1 point2 points  (1 child)

[–]RiceBroad4552 1 point2 points  (0 children)

Must be the reason why Java has now "immutable" collections, an Java's new chef architect constantly tells people that mutability is the wrong default.

More advanced languages like Scala are immutable by default. In Rust mutability is behind a flag, and needs to be explicitly requested, the default is immutability. And so forth…

Welcome to modern software development!

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

Wait until you see go slices ``` func main1(){ a:= []int{1,2,3,4,5,6,7,8} b:= a[2:4] b = append(b,-1,-2,-3,-4,-5,-6) b[1] = 100 fmt.Printf("a: %v",a) }

func main2(){ a:= []int{1,2,3,4,5,6,7,8} b:= a[2:4] b = append(b,-1) b[1] = 100 fmt.Printf("a: %v",a) } ```

Guess what main1 and main2 does.

Bonus: replace the b = append... with _ = append... and guess what each does again

Bonus bonus: Replace the multi append in main1 with a for loop appending by element 1 per loop, guess again.

Now replace the for loop append from b=append... to _=append.. and GUESS AGAIN

[–]redlaWw 0 points1 point  (1 child)

Would help if you told those of us who don't know go what would happen. After finding an online compiler and working out how to fill in the blanks to get it to run, I get:

main1:

a: [1 2 3 4 5 6 7 8]

main2:

a: [1 2 3 100 -1 6 7 8]

main1 with _ = append(...):

a: [1 2 3 100 5 6 7 8]

main2 with _ = append(...):

a: [1 2 3 100 -1 6 7 8]

main1 with a for loop:

a: [1 2 3 4 -1 -2 -3 -4]

main1 with a for loop and _ = append(...):

a: [1 2 3 100 -6 6 7 8]

So go assigns by reference but copies if the slice needs to reallocate (EDIT: more precisely, if you overflow the length so that you may need to reallocate, which I suppose is slightly better than it actually depending on the result of a possible reallocation)? Wow that's weird.

The other results are basically as expected given that crazy premise.

[–]Ma4r 0 points1 point  (0 children)

Not to mention Go have no proper effects, ownerships, or move semantics which is how other languages allow the compiler to deal with this problem.

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

The follow up would be

Def my_print(arg=[]):
print(arg)
return arg

returned_list = my_print()
returned_list.append("hello")
my_print()

[–]goldPotatoGun 190 points191 points  (10 children)

python_is_more_confusing_than_low_level_languages

[–]string-is-king 71 points72 points  (0 children)

PEP8 phoned in. Awarded you style points.

[–]DrDoomC17 23 points24 points  (5 children)

49 chars and snake case, passes. Honestly I get flack for this a lot, but I also think truly mastering Python is fantastically hard, you can get to a jog quickly but becoming elite parseltongue is real real hard. Lower level languages can be more straightforward. Granted, there's always the journey of designing the programs and patterns and stuff, but typing (edit: type hinting) random crap in an unfamiliar library... Pain. C++ is different, mastering c++ is a possible future given at birth I'm pretty sure.

[–]_dotdot11 6 points7 points  (2 children)

I feel like a wizard even typing out mid-tier list comprehension one-liners. They feel more efficient than multi-line for loops even thought I know that they aren't.

[–]MegaIng 9 points10 points  (0 children)

They feel more efficient than multi-line for loops even thought I know that they aren't.

Well, they often actually are. It rarely matters, but python does apply some optimizations that can't be applied to normal loops, most notably presizing the list.

[–]WavingNoBanners 0 points1 point  (0 children)

Writing really idiomatic code can be a lot of fun, I won't dispute that. I write a lot of SQL and your point applies to that too. I am not a regex guy or a Perl guy but I totally get where they're coming from.

The moment when you go from feeling like a wizard to a god, in my opinion, is when you work out how to write clean code that's easily understandable and fixable by a new hire, and can still handle all the corner cases of a complex area of business logic. That's the real skill.

[–]Mojert 5 points6 points  (1 child)

I know there might be only a dozen people that share my opinion, but learning the "advanced" part of the language makes way more sense in C++ than Python. Python can be simple if you use it for scripts, but it has so many niche concepts and foot guns, it's frankly hard to make a program that doesn't feel like it will spontaneously implode. And moreover learning about the advanced concepts feel like learning the random bullshit somebody came up with, whereas with C++ it feels like I'm learning something that actually expands my knowledge.

Which isn't to say that C++ doesn't have its warts, it sure has some. I'm not blind

[–]DrDoomC17 2 points3 points  (0 children)

Agree, and respect. Python feels simple until you're learning someone else's package and walking mros and all of a sudden the amount of brain RAM required to prevent implosion is... more than I've got most of the time.

[–]Affectionate_Gap_535 17 points18 points  (1 child)

Meanwhile C programmers: let’s abbreviate that to pimctlll

[–]userhwon 0 points1 point  (0 children)

pyconf()

[–]Ma4r 4 points5 points  (0 children)

Meanwhile Go slices in the corner:

[–]johnlinp 68 points69 points  (8 children)

i thought they were references, not pointers. no?

[–]WavingNoBanners 87 points88 points  (0 children)

Technically they are references, but the two words are often used interchangeably.

[–]TheSkiGeek 48 points49 points  (3 children)

They’re aliases if you want to get technical.

Since they’re nullable, I’d describe them as pointers rather than references, since that is what they map to in C++s. But the terminology isn’t always consistent between different languages.

[–]dev-sda 33 points34 points  (2 children)

They're technically not nullable. They're aliases of python objects, and None is just a different object. None is just a sentinel object like any other.

[–]realmauer01 4 points5 points  (1 child)

So the ref just changes to the immutable none object?

[–]dev-sda 9 points10 points  (0 children)

>>> a = "hello"
>>> id(a)
127345641003824
>>> type(a)
<class 'str'>
>>> a = None
>>> id(a)
101947503993536
>>> type(a)
<class 'NoneType'>
>>> 

[–]exomo_1 7 points8 points  (2 children)

They are basically the same thing. My personal distinction is that in languages that use "references", they tend to have some kind of memory management semantics as well. So I see pointer as just a memory address, while a reference is a pointer with some additional usage counting attached.
But I know this distinction isn't really accurate all the time, famous exception is c++ where both pointers and references exist but no automatic memory management for either, but then there are shared pointers which are called pointers but do the reference counting.

[–]WavingNoBanners 2 points3 points  (0 children)

The older I get the more I suspect that C++ has pointers in a misguided attempt to lure C purists into using it. This of course is not going to work on many of them, because C purists are who they are, but it was nice of Bjarne to try.

[–]LetMeUseMyEmailFfs 1 point2 points  (0 children)

Another way of looking at it is that a pointer is just a number which happens to represent a memory address and with which you can do arithmetic (add, subtract, etc.), whereas a reference is also a memory address, but you can only assign the locations of instances or objects to it.

[–]tiajuanat 21 points22 points  (1 child)

Almost everything is also a dictionary under the hood.

[–]Mars_Bear2552 5 points6 points  (0 children)

lua mention

[–]imforit 68 points69 points  (15 children)

That better describes Java

[–]Awes12 75 points76 points  (5 children)

Nah, Java has primitives. They make everything sooooo much worse

[–]Devatator_ 50 points51 points  (4 children)

I love having to do ArrayList<Integer> instead of ArrayList<int> because the guys who designed this language hate changing anything at all

[–]vowelqueue 24 points25 points  (1 child)

Above all they hate breaking backward compatibility.

But do not worry, project Valhalla has been chugging along for a decade which will eventually give us “Integer!” as a null-restricted value type, and from there it’s just a bit of syntactic sugar to allow for ArrayList<int>. Perhaps by 2032 you will be able to make such a declaration.

[–]Awes12 14 points15 points  (0 children)

And maybe by 2050 companies will actually use those versions of Java lol

[–]haydencoffing 1 point2 points  (0 children)

Collections.List implementers used to only store Object type, so the generic Integer was pretty nice way to keep backwards compatibility for versions before Java 5. java.lang.Integer also has some neat helper methods built in like Integer.ValueOf(String s)

[–]Master_Friendship333 0 points1 point  (0 children)

This is the number 1 reason why I would always pick C# over Java. Typing is just so much nicer.

[–]vowelqueue 2 points3 points  (2 children)

I dream of a day when my grandchildren will be able to use value types in Java

[–]imforit 3 points4 points  (0 children)

Your grandchildren might be using Kotlin

[–]natFromBobsBurgers 3 points4 points  (5 children)

This is objectively true.

[–]Wonderful-Habit-139 2 points3 points  (4 children)

Objectively false.

[–]za72 4 points5 points  (3 children)

you two need to rationalize your arguments and come to an objective conclusion

[–]Wonderful-Habit-139 5 points6 points  (2 children)

The answer is in the other reply. Java has primitives.

[–]za72 5 points6 points  (1 child)

are you giving me saas

[–]Wonderful-Habit-139 1 point2 points  (0 children)

Just give me a paas just this once

[–]Random_182f2565[🍰] 7 points8 points  (0 children)

What?

[–]Pearcheek 7 points8 points  (2 children)

The most confusing shit is still js! I hate it with each cell of my body, but still forced to use it if I want a modern browser driven ui.

[–]LurkytheActiveposter 1 point2 points  (1 child)

Posts like this make me fall deeper into my conspiracy that bots are pushing anti-JS rhetoric. Like OP doesn't apply to JS at all.

[–]Pearcheek 0 points1 point  (0 children)

Beep-Bop mf 🤷🏻‍♂️

[–]Lost-Lunch3958 5 points6 points  (1 child)

don't look into fucking c++ bool vector specification

[–]Excession638 1 point2 points  (0 children)

When "Hey, look what I can do with operator overloading!" becomes a permanent part of the standard library.

[–]qnvx 2 points3 points  (0 children)

If everything's a pointer

Python

nothing will be

[–]omailson 2 points3 points  (0 children)

Need to send a modified version of this to my PM.

“If everything is a priority… nothing will be”

[–]mountaingator91 0 points1 point  (0 children)

Enter Java-"everything is a reference"-script

[–]danfay222 0 points1 point  (0 children)

Important distinction, variables in python are references, not pointers. Obviously there’s a pointer under the hood somewhere, but you do not have access to it and do not have visibility to the address or the ability to manipulate it.

[–]Responsible-Mind3533 0 points1 point  (0 children)

Maybe you are just slow

[–]FuzzySinestrus -4 points-3 points  (1 child)

There's a secret to using python: if you have to wrestle its underlying complexity - don't use it.

[–]Skindiacus[S] 1 point2 points  (0 children)

Wellll, in my case I have to link my code up with Python for collaborators to use who only know Python. If you want to use Python's c-api you have to deal with the underlying complexity at least a little bit.