Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

(2) surprised me a little. Not because of the performance consequences, but because I almost never see explicit calls to `copy()` in Python (and I read a lot of Python).

I think maybe a more realistic example there would be people using splatting without realizing/internalizing that it performs a full copy, e.g.

    xs = [1, *ys] 
Another one that stood out was (3). Slots are great, but >95% of the time I'd expect people would want to use `slots=True` with dataclasses instead of manually writing `__slots__` and a constructor like that. `slots=True` has worked since Python 3.10, so every non-EOL version of Python supports it.


You can use __slots__ for normal classes; it’s not limited to only dataclasses.


I know that; that's why I said "I'd expect" not "you can't."


I didn't find 2 surprising either, but I'm a little surprised you never see it. If you want to treat the args to a function as immutable, what can you do besides copy, modify, and return a new object?


> what can you do besides copy, modify, and return a new object?

You can directly produce a modified copy, rather than using a mutating operation to implement the modifications.

It should be noted that "return a modified copy" algorithms can be much more efficient than "mutate the existing data" ones. For example, consider the case of removing multiple elements from a list, specified by a predicate. The version of this code that treats the input as immutable, producing a modified copy, can perform a single pass:

  def without(source, predicate):
      return [e for e in source if not predicate(e)]
whereas mutating code can easily end up with quadratic runtime — and also be difficult to get right:

  def remove_which(source, predicate):
      i = 0
      while i < len(source):
          if predicate(source[i]):
              # Each deletion requires O(n) elements to shift position.
              del source[i]
          else:
              # The index increment must be conditional,
              # since removing an element shifts the next one
              # and that shifted element must also be considered.
              i += 1


swap with last element then truncate at the end


Yes, you can do this if you don't care about order, and avoid the performance degradation. But it's even more complex.

Or if you do care about order, you can emulate the C++ "erase-remove" idiom, by keeping track of separate "read" and "write" positions in the source, iterating until "read" reaches the end, and only incrementing "write" for elements that are kept; and then doing a single `del` of a slice at the end. But this, too, is complex to write, and very much the sort of thing one chooses Python in order to avoid. And you do all that work, in essence, just to emulate what the list comprehension does but in-place.


You can do the in place variant using generator comprehension and writing the result in place in the original vector. The generator should run ahead of the write and should work fine.

It will also probably be significantly slower than just copying the vector.


I think copying and modifying is normal! I just almost never see it with `copy()`. Apologies if I didn't say that clearly.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: