14 ms·
Python: Overlooked core functionalities
- qsort 3y agoThe big missing item from the list: generators! Using "yield" instead of "return" turns the function into a coroutine. This is useful in all sorts of cases and works very well with the itertools module of the standard library. One of my favorite examples: a very concise snippet of code that generates all primes: def primes(): ps = defaultdict(list) for i in count(2): if i not in ps: yield i ps[i**2].append(i) else: for n in ps[i]: ps[i + (n if n == 2 else 2*n)].append(n) del ps[i]
- erikvdven 3y agoThanks! I tried to add mostly the stuff I don't encounter that often in blogs/tutorials etc. But guess you are right. Generators, or at least the 'yield' keyword, is often misunderstood, and we can't emphasize them enough
- qsort 3y agoJust to clarify, I don't mean your article is bad or incomplete -- quite the contrary, I enjoyed it a lot. Generators are one of my favorite Python features and they're kind of underused, mostly because people simply don't know about them. A couple more along the same lines: - Metaclasses and type. (This is admittedly dark magic, but useful in library code, less so in application code) - Magic methods! Everyone knows about __init__, but you can override all sorts of behaviors (see: https://docs.python.org/3/reference/datamodel.html https://docs.python.org/3/reference/datamodel.html) My favorite example (I have a lot of favorite examples :)) is __call__, which emulates function calling and is the equivalent of C++'s operator(). Why is it my favorite? Because as the old adage goes, "a class is a poor man's closure, a closure is a poor man's class": class C: def __init__(self, x): self.x = x def __call__(self, y): return self.x + y >>> a = C(2) >>> a(3) 5
- learplant 3y agoI find that __call__ is very confusing, but maybe because I'm not used to seeing if often. What is the benefit compared to having a method named "add" that also explains the behavior?
- Jtsummers 3y agoIf an object is callable you can use it in places that might conventionally expect functions. The utility of that is very situational, though. I've only used it a handful of times myself over the years I've known and used Python. It may also give you a "clearer" (in quotes because subjective) presentation for something you're trying to do.
- claytonjy 3y agoI see it a lot in HuggingFace, and use it myself for classes that are used like a function, especially when the obvious method name is the verb form of the class name processor = SomeProcessor.load("path/to/config") # with __call__ processed_inputs = processor(inputs) # less awkward than processes_inputs = processor.process(inputs) The only benefit is to the human, same as @property or even @dataclass.
- learplant 3y agoThanks for writing that up! I disagree though, I prefer the processor.process for clarity, and for not adding another way of doing things that regular methods already do.
- erikvdven 3y agoThanks a lot! Really appreciate it. Love the example! Haven't used the dunder __call__ yet (like many magic methods I guess), but that's a nice one! I didn't have to use Metaclasses, either, though I have read about them, especially in Fluent Python. But I guess I belong to the 99% who haven't had to worry about them, yet :P
- atoav 3y agoAnd this is a presentation explaining why generators may be extremely useful for all kind of data pipelines: https://www.dabeaz.com/generators/Generators.pdf https://www.dabeaz.com/generators/Generators.pdf If you don't know it already, it is really worth looking into. I am a python dev with nearly a decade of experience and I knew generators, and yet this was still an eye opener.
- thenberlin 3y agoWow, thanks for that -- that's an excellent slide deck.
- azeirah 3y agoNote that despite this being a python-specific slide deck, generators and iterators are also present in many other languages, including but not limited to Rust and JS. The concepts matter more than the chosen language in this deck. I learned a lot! Looks like I can apply this to a PHP trace/profile parser project, especially the pipelined parsing and the query language idea.
- slt2021 3y agocan you explain how generators work with multiprocess (Thread based pool) ? is ps internal variable unique for each Thread or same? is it safe to execute your primes() from different threads?
- TrianguloY 3y agoA yield will simply return a generator object, which contains information about the next value to use, and how to continue the function execution. That's why you need to use functions that yield things inside loops or list(...). If you run it from different threads I guess it will be the same as calling the function multiple times, it will return a new started-from-the-top generator. def sum(): yield 1 yield 2 print(repr(sum())) print(next(sum())) print(next(sum())) Prints <generator object sum at 0x7fc6f14823c0> 1 1
- slt2021 3y agoso Thread based based pool will have same instance of generator, while Process based pool with have unique instance of generator?
- TrianguloY 3y agoI don't know if a generator can be shared across threads, but in that case ... I have no idea :/ You'll need to search, or try!
- kzrdude 3y agoIn this example, calling sum() creates a generator and returns it. Say g = sum(). If you share g between threads, they will all use the same generator object! If you call sum() separately per thread, they will be different generators. If you try to send g to a different process, you will get an error, because it doesn't serialize.
- qsort 3y ago> can you explain how generators work with multiprocess The best way to think of a generator is as an object implementing the iteration protocol. They don't really interact with concurrency, as far as multiprocess is concerned, they're just regular objects. So the answer is that it depends on how you plan to share memory between the processes. > is ps internal variable unique for each Thread or same? ps is local to the generator instance. def f(): x = 0 while True: yield (x := x + 1) >>> f() <generator object f at 0x10412e500> >>> x = f() >>> y = f() >>> next(x) 1 >>> next(x) 2 >>> next(y) 1 > is it safe to execute your primes() from different threads? For this specific generator, you would run into the GIL. More generally, if you're talking about non CPU-bound operations, you need to synchronize the threads. It's worth looking into asyncio for those use cases.
- TrianguloY 3y agoBut wait, there's more, you can send data back to the function! (Will be returned as the yield output) https://stackoverflow.com/questions/20579756/passing-value-to-yield-using-send#20579767 https://stackoverflow.com/questions/20579756/passing-value-t... And don't forget "yield from" (same as yielding all values in a list, but keeps the original generator! You can send data back to the list if it is itself another generator!)
- noitpmeder 3y agoAnyone have good examples of how/when to actually use this? I've personally never interacted with or written a generator that expects to receive values.
- pizza 3y agoAnything with feedback control. Updating a priority queue's weights, adaptive caching, adaptive request limiting, etc. Ironically it looks like HN itself rate limited me the first time I tried to reply lol
- luckycharms810 3y agoThis is the basis of most older async frameworks (see: Tornado, Twisted). A while ago I put together a short talk on how to go from this feature -> a very basic version of Twisted's @inline_callback decorator. https://github.com/ltavag/async_presentation/tree/master https://github.com/ltavag/async_presentation/tree/master
- strunz 3y agoI actually had a great use case for this last week. Needed to flatten a list of nested dicts, e.g.: [ {"name": "/dev/loop0"}, {"name": "/dev/loop1"}, {"name": "/dev/loop2"}, { "name": "/dev/sda", "children": [ { "name": "/dev/sda1", "children": [{"name": "/dev/mapper/lubuntu--vg-root"}, {"name": "/dev/mapper/lubuntu--vg-swap_1"}], }, ], }, {"name": "/dev/sdb", "children": [{"name": "/dev/sdb1"}, {"name": "/dev/sdb2"}]}, {"name": "/dev/sdc", "children": [{"name": "/dev/sdc1"}, {"name": "/dev/sdc9"}]}, ] Wound up writing a recursive generator (with some help from #python on IRC): def flatten(items): for item in items: yield {k:v for k,v in item.items() if k != 'children'} if 'children' in item: yield from flatten(item['children']) which results in: [{'name': '/dev/loop0'}, {'name': '/dev/loop1'}, {'name': '/dev/loop2'}, {'name': '/dev/sda'}, {'name': '/dev/sda1'}, {'name': '/dev/mapper/lubuntu--vg-root'}, {'name': '/dev/mapper/lubuntu--vg-swap_1'}, {'name': '/dev/sdb'}, {'name': '/dev/sdb1'}, {'name': '/dev/sdb2'}, {'name': '/dev/sdc'}, {'name': '/dev/sdc1'}, {'name': '/dev/sdc9'}]
- gurchik 3y agoI like using generators when querying APIs that paginate results. It's an easy way to abstract away the pagination for your caller. def get_api_results(query): params = { "next_token": None } while True: response = requests.get(URL, params=params) json = response.json() yield from json["results"] if json["next_token"] is None: return params["next_token"] = json["next_token"] for result in get_api_results(QUERY): process_result(result) # No need to worry about pagination
- gcanyon 3y agoI think I figured out that count(2) is from itertools? I'm new to python. I think you could simplify the rest like so: def primesHN(): from collections import defaultdict from itertools import count yield(2) ps = defaultdict(list) for i in count(3,2): if i not in ps: yield(i) ps[i**2].append(2*i) else: for n in ps.pop(i): ps[i + n].append(n)
- qsort 3y ago> I think I figured out that count(2) is from itertools? It is. Itertools is a masterpiece of a module. It has a lot of functions that operate on iterators and will work both on standard iterables (lists, tuples, dicts, range(), count() etc.) and on your own generators. It forms a sort of "iterator algebra" that makes working with them very easy. > I think you could simplify the rest like so: Sounds good, but with a caveat: you do need to call "del" at the end for memory deallocation purposes. The garbage collector isn't smart enough to know you won't be using those dictionary entries any longer. Technically the code still works, but keeping everything in memory defeats the purpose of writing a generator.
- gcanyon 3y ago> you do need to call "del" at the end The garbage collector doesn't understand "pop"? That seems...dumb? ¯\_(ツ)_/¯
- atxbcp 3y ago- none of these functionalities are "overlooked", this is pretty basic python - for fibonacci you have a decorator for memoization (functools cache / lru_cache) - you don't need to use parenthesis for a single line "if"
- agumonkey 3y agoYou consider these 'basic' python ? just curious, I'd say it's a bit below intermediate.
- OJFord 3y agoAt the point we're disagreeing about 'basic' vs. 'bit below intermediate'.. idk we at least have to agree how many levels the model has. Fwiw I also thought it was pretty regular stuff, and then arcane library functions you've either needed or you haven't. Also, that's a generator, not a list comprehension.
- elteto 3y agoOne man's basic is another man's low intermediate.... but I agree that none of these seem overlooked to me,. They are pretty basic things once you get past the few chapters of your first python book.
- erikvdven 3y agoYou are very much right a lot of it is pretty basic knowledge. From my experience though, a lot of python developers don't take the python docs or tutorial as first resource, and quite some developers I met did lack quite some knowledge I mentioned in the article. You are right about the fibonacci operator, I thought I did refer to another article where I mention the lru_cache as well :) But I'll double check. Good one about the parenthesis! I'll post an update soon
- rowanseymour 3y agoSince Python 3.7 import pdb pdb.set_trace() can be written as just breakpoint()
- agumonkey 3y agoI was told that at my job, but my fingers are so used to type `pdb` and emacs template-replacing it that I can't change.
- shpx 3y agoConfigure Emacs to template-replace it with breakpoint()
- erikvdven 3y agoThanks for the tip! :)
- IshKebab 3y agoAnd this also works with Debugpy so you can actually use a proper debugger and not pdb which is frankly terrible.
- masklinn 3y agoThat's not entirely true, because `breakpoint` is a more general hook, `pdb.set_trace` is just its default behaviour. This is, if anything, better. Because that way you can e.g. replace stray `breakpoint()` calls by warnings rather than break production :D
- VWWHFSfQ 3y ago> Python arguments are evaluated when the function definition is encountered. Good to remember that! I would never try to exploit this behavior to achieve some kind of benefit (avoiding max recursion). Any tricks you try to do with this is almost definitely going to to cause bugs that are very difficult to track down. So don't be too clever here.
- hangonhn 3y agoYeah. I was really surprised to see this as a feature to be used rather than a gotcha. I've seen it more as gotchas, as in actual bugs introduced because of this behavior, and never as a feature until now. I can see why he thinks it's useful though and, maybe within his specific context, it is. That said, even for his example, I think he would have been better off using https://docs.python.org/3/library/functools.html#functools.cache https://docs.python.org/3/library/functools.html#functools.c...
- erikvdven 3y agoYou are right about that, perhaps it is good to mention it as "gotcha". Or I could have used a better title. I do think though, it is good practice to know this stuff. About the cache decorator: I did link to another article where I discuss lru_cache and cache :)
- progmetaldev 3y agoBefore I saw your comment, I had "overlooked" that these were presented as beneficial features, rather than just curiosities. As someone just learning Python, but familiar with other languages, I can only hope that if I start using Python in production with other developers they take the most obvious route (or use a comment as to why they would be relying on this type of behavior). I chose to learn Python because it seemed to be the easiest to read, which to my mind meant working in a team would lead to easier discovery and understanding. Then I see articles like this, and wonder if I'll have a lot of footguns to watch out for where the code isn't as clear as it seems.
- 3y ago
- m4r71n 3y agoI would not recommend the default arguments hack. Any decent linter or IDE will flag that as an error and complain about the default argument being mutable (in fact, mutable default arguments are the target of many beginner-level interview questions). It's much easier to decorate a function with `functools.cache` to achieve the same result.
- Smaug123 3y agoMore concretely, one of the classic Python bugs is to use `[]` as a default argument and then mutate what "is obviously" a local variable.
- nighthawk454 3y agoI think it's even more safe/preferable to use non-mutable `None`s as a default and do: ``` def myfunc(x=None): x = x if x is not None else [] ... ```
- memco 3y agoIn some cases you can also do: x = x or [] Your method is best when you might get falsy values but if that’s not an issue the `or` method is handy.
- aunderscored 3y agoI tend to dislike this method as it's unclear what or returns unless you already know that or behaves this way. x if x is not None else default is cleaner in my opinion
- gcanyon 3y agoI'm learning python, and I hit this milestone about a week ago!
- at_compile_time 3y agoWhat's it do?
- barrenko 3y agoUse all this and you've got yourself a poor man's Ruby.
- stabbles 3y ago> Python arguments are evaluated when the function definition is encountered This is a giant pain. Easy to miss. Sometimes forces you to deal with Optional[Something] instead of just Something. Compare with Julia where default arguments are evaluated ... very late: julia> f(a, b, c, d = a * b * c) = d f (generic function with 2 methods) julia> f("hello", " ", "world") "hello world" that's really neat.
- progmetaldev 3y agoProbably one of the benefits I gained from writing JavaScript before ES5 (although have worked with many languages, I've only used a few that were dynamic - PHP, JS, and old VB). I write my functions as early as possible, having remembered hoisting rules from JavaScript (and trying to only rely on OOP with Python where it naturally makes sense). Looking at your Julia example, this seems much more friendly and less surprise and error-prone.
- TrianguloY 3y agofirst, _, last = [1, 2, 3, 4, 5] I guess this is a typo, it should be first, *_, last = [1, 2, 3, 4, 5] (As explained above!) Other than that, nice list of python tricks, I love not-so-known features because it can make code shorter and prettier!
- erikvdven 3y agoSharp! Updated that line. And thank you for the compliment :)
- ivalm 3y agoMultiple context managers in a single with statement is something I didn’t know!
- robertlagrant 3y agoThis is a bit of bikeshedding, but I think if not n in memo: is more naturally written as if n not in memo:
- progmetaldev 3y agoAs someone learning Python, but having worked with other languages, I think your second example is better as it reads more like English. I think that simplicity actually ends up much more rewarding when it comes to reading code.
- alfalfasprout 3y agoI would absolutely point this out in a code review. It's not even that pedantic, it's the kind of code that causes a double take b/c.
- kstrauser 3y agoI agree. The first is order of operations dependent. Without looking, is that `(not n) in memo` or `not (n in memo)`? The sent can only be interpreted one way.
- stavros 3y agoLinters agree with you with the default config, and will warn on "if not x in y".
- zbentley 3y agoAgree. Using "not in" can also theoretically make certain checks faster (e.g. testing negative presence in a hash-based data structure can bail out without walking the collision chain if the initial hashed location does not have an element).
- MayeulC 3y agoHmm, I encountered or used all of these somewhere, but 4 days ago I learned something else: python natively supports complex numbers. a=1+3j b=a+4j I encountered this when a friend noticed some weird syntax for a numpy meshgrid (via mgrid): np.mgrid[-1:1:5j]
- TylerE 3y agoRepr prints source code that will (often) give you an equivalent object. I would be highly surprised if it got you The same object instance. == but not ===
- kstrauser 3y agoThat parses to me as "given the list np.mgrid, return indexes -1 through 1, step by 5j". I know it's not, but that's what it looks like to me.
- kzrdude 3y agoNumpy has a lot of these shortcuts that are quite opaque. For example np.r_ and np.c_ This one can be explained as "equivalent to np.linspace(-1, 1, 5)", i.e 5 evenly spaced points between -1 and 1. Normally the step size is an integer but with a complex "step" it switches the meaning from step size to number of equidistant points.
- zwieback 3y agoGreat list, thanks, I'll be sure to use some of these. Here's the obvious question: how many more unknown-but-useful features are hidden away in other similar articles.
- erikvdven 3y agoToo many I bet. There is always stuff to learn I guess, but reading Fluent Python gets you pretty far. @qsort also mentioned some nice extras.
- IshKebab 3y ago"Overlooked core functionality" is an interesting way to spell "massive footgun".
- QuiDortDine 3y agoI've been coding for most of my life and I can't believe some people would choose some of these tricks when python has much simpler syntax for most of these. The first, *_, last trick for example would be particularly obnoxious to encounter. The first element is my_list[0], last is my_list[-1]. Dead simple, way easier to understand at a glance.
- masklinn 3y agoAnd does not work at all on iterators.
- ziedaniel1 3y agoA couple details worth noting: - `repr` often outputs valid source code that evaluates to the object, including in the post's example: running `datetime.datetime(2023, 7, 20, 15, 30, 0, 123456)` would give you a `datetime.datetime` object equivalent to `today`. - Using `_` for throwaway variables is merely a convention and not built into the language in any way (unlike in Haskell, say).
- carabiner 3y agoWould add: * For dicts, learn .setdefault() vs. .get() vs. defaultdict() * .sort(key=sortingkey) * itertools groupby, chain * map, filter, reduce
- erikvdven 3y agoPerhaps for another article? :) But thanks, definitely a nice list! My purpose for now was to keep the article 'digestible' as well.
- was_a_dev 3y agoThe walrus operator isn't overlooked imo. It's more that many still haven't updated to >3.8
- amethyst 3y ago3.7 was released in 2018 and is already EOL. Those folks should probably start considering an upgrade...
- nextlevelwizard 3y agoEven if the environment updates and you could use new stuff it takes time for you to rediscover this stuff after the upgrading and start using it.
- BoppreH 3y agoPretty good list. Two corrections: The `first, *middle, last` trick doesn't work if your list only has one element: first, *middle, last = [1] ValueError: not enough values to unpack (expected at least 2, got 1) And the last title has a typo: > Separater for Large Numbers
- nemetroid 3y agoI don't think the point about splat unpacking really is a correction. Unpacking always requires that the iterable has enough values to assign to the specified variables, this has nothing to do with the use of *middle.
- BoppreH 3y agoTo be clear, this is only a problem if your array can ever have less than two values, or if you require that `first` and `last` be different values. The mistake is suggesting the unpacking without caveats, because it'll fail in situations that the naive solution doesn't: items = [1] first = items[0] middle = items[1:-1] last = items[-1] This will still work as long as you have any elements.
- mkl95 3y agoSome of these features lie in the border of the uncanny valley where languages like Ruby and "vanilla Javascript" live, and are not compatible with the principle of least surprise or even the Zen of Python. I don't write too much Python anymore, but when I do I keep it simple and explicit.
- dajt 3y agoI find a lot of python like that. It's a simple language to get started in but an incredibly complex language to try and get across more than skin deep. Maybe not C++ complex but more than I expected. It has some wild features and crazy syntax and if you know it, it's probably awesome, but I too like to keep it mostly simple and obvious.
- erikvdven 3y agoI agree. Someone else here also mentioned that they prefer code that is easy to read over code that uses a lot of "unfamiliar functionality," let's call it that. And I do agree; Kyle mentions the same thing if I remember correctly when it comes down to JavaScript. It is better not to expect your colleagues or other developers to know the ins and outs of the language as well. If one way is 10x easier to understand, just stick with that. But as you said: if you know it, it's probably awesome. In my opinion, it never gets boring to discover new things in Python, and it does make you a better Python developer. Knowing what and when to apply certain knowledge is where your experience comes in.
- version_five 3y agoRe unpacking with * one I use often is when you have a list of types of coordinates you want to plot, i.e. # z = [(x0,y0), (x1,y1) ...] You can do import matplotlib.pyplot as plt plt.plot(*zip(*z)) I spent years doing x = [t[0] for t in z] # etc before I realized this.
- jacurtis 3y agoI'd argue that your original approach is actually better than your new approach. Using a list comprehension, such as your original approach, is pretty easily understood by anyone writing python and is easy to follow, it is also quite terse. Your recursive unpacking zip thing is much harder to understand and read. This reminds me of the type of stuff you find in the codebase years later when the person who wrote it is long gone and you find a comment next to it that says: # No idea why this works, but don't touch it One of the problems I have with python is that there are a million super creative ways to do stuff, especially using less known parts of the language. People love to get super creative with it, but usually the simplest solution is actually the best one, especially when working on a team. In your example above, you aren't even saving any real space. Both approaches can be done inline, the list comprehension is maybe a few extra characters. You're not really saving anything, just making it harder to read and maintain by others. When I moved from a company that wrote in Python to one that wrote in Golang, I found that the restrictions that Golang offers is a huge benefit in a team. Because you don't have access to all these crazy language components that python has, the code written in Go would be almost identical regardless of who wrote it. Of course everything in Golang is far far more verbose than Python, but I actually found it 100x more maintainable. In the python codebase it was very easy to tell who wrote different parts of a codebase without looking at the git blame, because there was almost a "voice" with the style of writing python. But in Golang it was more restrictive which meant that the entire codebase was more cohesive and easily to jump around.
- deleted 3y ago[deleted]
- abecedarius 3y agoThe need actually comes up a lot to transpose a list of lists. That zip can do it is not hard to visualize and it's an idiom worth learning. If it still seems unclear, you can name things to help: columns = zip(*rows) or def transpose(list_of_lists): return zip(*list_of_lists) But anyway, yeah, tastes differ, it's fine if we disagree. I do agree that Python has gotten uncomfortably complex. But this is a very old feature from simpler times and does not add any syntax or metaprogramming features, it's just an already needed function.
- skitter 3y agoI'm annoyed at the reason that any/all have to be on this list. If they (and map, filter, …) were methods, you could just write `foo.` and your IDE could show you what methods are available. Postfix would make things easier to read too: bar.baz()\ .filter(some_filter)\ .map(some_op)\ .min()\ .foo() Data/control flows from top to bottom. One operation per line. But with freestanding functions: min(map(some_op, filter(some_filter, bar.baz()))).foo() To follow the flow of data/control, you start in the middle, go right, then skip left to filter, read rightwards to see which filter, skip left to map, read rightwards to see what map, go left to min, then skip all the way to the right. Just splitting it into multiple lines doesn't help, you need to introduce intermediate variables (and make sure they don't clobber any existing ones) and repeat yourself whether they clarify things or not. The same issue exists for list/dict/set comprehensions.
- KMnO4 3y agoWould that really work? You can chain those functions because they return the same type. For example, filtering a list returns a subset of the list. Any/all return a Boolean, so the chain would stop there. I also personally think any(x % 5 in range(y)) Is more clear than range(y).any(lambda x: x % 5)
- zmmmmm 3y agoit's interesting I completely agree with you and it's a big reason I find Python irritating to write (compared to Groovy, Kotlin, Ruby, etc). However there do seem to be a lot of people that dislike this method chaining style and will assert that functional style is better in every way. But I just can't fundamentally agree that writing these as functions is as readable. Even if you go far out of your way to format it similarly, it still forces you to do a lot of mental work to see the inner most starting point and then deduce what the sequence of operations that happens is backwards, eg: foo( min( map(lambda x: ..., filter(lambda: y: ...., baz(bar) ) ) ) (and of course, the python linters are typically configured to hate this so you can't realistically write it this way even if you want to)
- llwu 3y ago
- aorist 3y ago> The underscore _ can be used as a throwaway variable to discard unwanted values: So can any other variable, using underscore is just a convention to make it obvious that you're not planning to re-use it (it doesn't get GCed more aggressively or anything). Similarly, private methods being prefixed with an underscore is also just a convention, you can access them from anywhere. However, double underscores are used for magic attributes and name mangling for class attributes, which are interpreted differently! (See: https://stackoverflow.com/a/1301369 https://stackoverflow.com/a/1301369)
- noitpmeder 3y agoMany linters are also configured to ignore '_' for many tests (such as any 'unused variable' warnings)
- Fatnino 3y agoIn the interactive environment _ automatically holds the value of the last statement executed. >>> 1+1 2 >>> _ * 3 6
- KRAKRISMOTT 3y agoIn newer versions of Python, pdb.set_trace() is automatically aliased to the top level breakpoint() function. Your no longer need to import pdb.
- hwayne 3y agoA couple people already pointed out that you can write `breakpoint()` instead of using `pdb.set_trace()`. Here's one more trick: you can use `pdb` to run scripts! `python -m pdb foo.py` will run `foo.py` but trigger a breakpoint on the first error.
- tomtom1337 3y agoOh! Thats a really nice one!
- erikvdven 3y agoThat is definitely a neat one! If you are ok with it, I might add that one. I just updated the article already with some of the great comments and tips I recieved over here.
- carapace 3y agoSlice notation can be used on the left-hand side of assignment.
- ks2048 3y agoI know it's a really minor point, but in a blog post about Python (rather than just one that is using Python), it kind of bothers me to see "non-Pythonic" code style, if(x > 0): ... vs if x > 0: ... but probably just OCD kicking in.
- andreareina 3y agoMutable default arguments is widely regarded to be a footgun. I agree.
- physicsguy 3y ago> Because the language is so easy to learn, many practitioners only scratch the surface of its full potential, neglecting to delve into the more advanced and powerful aspects of the language which makes it so truly unique and powerful We have definitely found this to be true in hiring. Many people’s Python knowledge seems to just be surface deep.
- Barrin92 3y agoas an alternative to pdb I like to use `import code; code.interact(local=locals())` Drops you into the interpreter and is sufficient for a lot of debugging problems.
- est 3y agothe most surprising feature I learned about Python's core functions was enumerate() had a "start" parameter. I wrote countless +1 offsets.
- crabbone 3y agoOverlooked by whom? Even though most of the stuff OP writes about is worthless, I see it used a lot (if it's old) and less so, but still enough otherwise... This article reads to me like as if it was written by someone learning Python, perhaps in their 3'rd-4'th month, when they finally decided to open documentation / some existing project code instead of implementing calculators and animal class hierarchies...
- oars 3y agoArticles like this are gold. Thanks!
- erikvdven 3y agoThank you so much! And discussions about these articles over here are even more valuable :)
- bjornasm 3y agoimport random some_value = 9 # return a number between 0 and, including, 100 if below_ten := some_value < 10: print(f"{below_ten}, some_value is smaller than 10") Random isn't used in this function, but more importantly why would you assign the value to below_ten if the point is to just print it, why not just print some_value? Even in the next example of the walrus operator - it is extremely contrived: if result := some_method(): # If result is not Falsy print(result) Why not just: if some_method(): print(True)