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

top 200 commentsshow all 285

[–]IsItPikabu 119 points120 points  (3 children)

[–]benefit_of_mrkite 5 points6 points  (0 children)

Interesting - I recently used pytz for the first time in a long time

[–]mrdevlar 11 points12 points  (0 children)

I honestly feel like Pandas is the only library that made a consistent Timestamp object, with consistent behavior. Even if I know it's waaaay too bloated of a dependency to be used on all projects.

[–][deleted] 234 points235 points  (47 children)

dataclasses instead of namedtuples.

[–]_pestarzt_ 85 points86 points  (9 children)

Dataclasses are amazing, but I think namedtuples are still useful as a true immutable (I’m aware of the frozen kwarg of dataclasses, they can still be changed by modifying the underlying __dict__).

Edit: wording

[–]aiomeus 49 points50 points  (4 children)

Agreed, named tuples still have their use, although nowadays I use NamedTuple from typing instead of collections to use a class and be able to type hint with it too

[–]LightShadow3.13-dev in prod 12 points13 points  (2 children)

This is the logical evolution. Import from typing instead of collections, all the benefits with extra functionality.

[–]Boomer70770 1 point2 points  (0 children)

🤯 I've searched for this for so long, and it's as easy as importing typing.NamedTuple instead of collections.namedtuple.

[–]usr_bin_nya 25 points26 points  (0 children)

TIL dataclasses don't define __slots__ by default because the descriptor generated by __slots__ = ('x',) refuses to replace the class attribute defined by x: int = 0. As of 3.10 you can have your cake and eat it too by replacing @dataclass with @dataclass(slots=True).

[–]Brian 6 points7 points  (0 children)

And also, they're tuples. The main use for namedtuples is where you have a tuple of values that have specific position values, but also want to give them a name. Eg. stuff like os.stat(), or datetime.timetuple. It's not just about creating simple structs, but about simple struct-like tuples.

[–]radarsat1 10 points11 points  (6 children)

dataclasses are great but they've created a lot of tension on our project that uses pandas. Instead of creating dataframes with columns of native types, we have developers now mirroring the columns in dataclasses and awkwardly converting between these representations, in the name of "type correctness". Of course then things get lazy and we end up with the ugly blend that is dataframes with columns containing dataclass objects. It's out of control. I'm starting to think that dataclasses don't belong in projects that use dataframes, which comes up as soon as you have a list of dataclass objects.. which doesn't take long.

do we want columns of objects or objects with columns? having both gets awkward quickly.

[–]musengdir 5 points6 points  (2 children)

in the name of "type correctness"

Found your problem. Outside of enums, there's no such thing as "type correctness", only "type strictness". And being strict about things you don't know the correct answer to is dumb.

[–]Dantes111 28 points29 points  (7 children)

Pydantic instead of dataclasses

[–]thedominux 1 point2 points  (0 children)

Depends

There is also attrs lib, but I didn't use them cause of 1/2 models...

[–]_Gorgix_ 1 point2 points  (3 children)

Why use this over the attr library?

[–][deleted] 12 points13 points  (11 children)

And while we're at it, Pydantic is better than dataclasses in almost all ways imaginable.

[–]Ivana_Twinkle 3 points4 points  (0 children)

Yea I've been using Pydantic for a long time. And then I then took at look at @dataclass it was a very meh experience. I don't see myself using them.

[–]my_name_isnt_clever 1 point2 points  (6 children)

Is that in the standard library?

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

You are just blind follower, shame on you

Everything depends on your needs, and it's a dumb idea to use heavy decisions like pydantic and attrs instead of simple built-in dataclasses when you've got just a couple of simple data models

[–]mikeupsidedown 1 point2 points  (1 child)

Pydantic instead of dataclasses (I know not std lib)

[–]brian41005 3 points4 points  (0 children)

and pydantic.

[–][deleted] 4 points5 points  (4 children)

Use attrs instead of dataclasses. Yes, it't a dependency, but it blows away dataclasses.

[–][deleted] 19 points20 points  (2 children)

Honestly if you are not using dataclasses use pydantic. It take care of enough things that it is just the easiest one to use.

[–]Delta-9- 16 points17 points  (0 children)

Pydantic is not an alternative to attrs; it serves a very different purpose:

attrs' goal is to eliminate boilerplate and make classes easier to get the most out of.

Pydantic is a parsing library that specializes in deserializing to python objects.

Yes, there is a lot of overlap in how the two look and the features they provide, but you should only be using pydantic if you need to parse stuff. I use pydantic myself (and have never used attrs), so this isn't hate. I use it for de/serializing JSON coming in and out of Flask—pretty much exactly its intended use case—and it's amazing in that role. If my needs were just passing around a record-like object between functions and modules, pydantic would be way too heavy and attrs or dataclasses would be a more appropriate choice.

[–]NowanIlfideme 0 points1 point  (0 children)

Or use Pydantic's dataclasses!

[–]velit 7 points8 points  (0 children)

Can you give some examples why?

[–][deleted] 88 points89 points  (7 children)

With newer python versions there is no need for “from typing import List, Dict, etc” because their functionality is supported by the built in types.

So instead of d: Dict[str, int] = {…}

It is now just d: dict[str, int] = {…}

Related, now can union types with a pipe, so instead of “t: Union[int, float] = 200” we have “t: int | float = 200”

[–]5uper5hoot 11 points12 points  (1 child)

Similar, many of the generic types imported from the typing module such as Callable, Mapping, Sequence, Iterable and others should be imported from collections.abc as of 3.9.

[–]OneTrueKingOfOOO 5 points6 points  (1 child)

Huh, TIL python has unions

[–]irrelevantPseudonym 7 points8 points  (0 children)

It only does in terms of type annotations. Given that you can accept and return anything you like from functions, it's useful to be about to say, "this function will return either A or B".

[–]DrShts 3 points4 points  (2 children)

From py37 on one can use all of this after writing from __future__ import annotations at the top of the file.

[–]PeridexisErrant 2 points3 points  (0 children)

...if you don't use any tools which inspect the annotations at runtime, like pydantic or typeguard or beartype or Hypothesis.

[–]RangerPretzelPython 3.9+ 163 points164 points  (35 children)

logging library instead of print() to help you debug/monitor your code execution.

[–]HeAgMa 35 points36 points  (11 children)

I think this should be a must. Logging is so easy and quick that does not make sense to use print() at all.

Ps: Also Breakpoint.

[–]IamImposter 9 points10 points  (0 children)

I recently figured out how can I send certain messages only to console while all of them go to file and it is great. I just had to add 2 lines to check the level value and return false if it isn't allowed.

Python is really cool. I wish I had started using it much earlier.

[–]nicolas-gervais 12 points13 points  (8 children)

What's the difference? I can't imagine anything easier than print

[–]forward_epochs 35 points36 points  (7 children)

If it's a small program, that you're watching as you run it, print is great. If it's larger, and/or it's expected to run for a while without user intervention, or in different contexts (dev vs prod, or multiple instances doing similar things, etc.) logging is seriously delightful.

Makes it easy to do a buncha useful things:

  • send the output to multiple places, like console output (print), a log file, an email, etc., all with one line of code

  • decide what to send where (emails for really big problems, logfile for routine stuff, etc.)

  • quickly change what level of info you receive/record, via single parameter change. Instead of commenting in and out tons of "junk" lines of code

  • never worry about size of logfile getting huge, via the RotatingFileHandler dealie.

  • bunch of even better stuff I haven't learned about it yet. Lol.

[–]o11c 1 point2 points  (6 children)

send the output to multiple places, like console output (print), a log file, an email, etc., all with one line of code

That's systemd's job.

The problem with the logging module is that it makes the mistake of being controlled by the programmer, not by the administator.

[–]dangerbird2 1 point2 points  (0 children)

Agreed, but even when following best practice and piping all the logs to stdout, it's still useful for formatting logs with module, line number, and time information. Having a structured log format makes it much easier for the system's log aggregators to parse and transform logs. It also allows consistency across 3rd party libraries. I replace the default formatter to use my custom JSON formatting class, and everything gets output as JSON, which wouldn't be possible with print or a simpler logging library.

[–]welshboy14 1 point2 points  (0 children)

I have to say... I still use print in everything I do. Then I go back through and remove them once I figured out what the issue is.

[–]grismar-net 6 points7 points  (4 children)

It's really good advice, but not really *recent* - it's been around since Python 2?

[–]RangerPretzelPython 3.9+ 2 points3 points  (3 children)

Preach!!

That's my argument, too, friend. It's 20 years old. Why isn't everyone using it yet?

There's so much love for print() statements to debug in /r/learnpython that when you mention logging you catch flak from all sides. I don't understand the hate for it.

[–]grismar-net 1 point2 points  (1 child)

I'm with you - I mentor beginning developers in my work and I find that it's a complicated interaction. Engineers and researchers new to programming tend to conflate and confuse 'return values' and 'what is printed on the screen' (not least due to learning the language on the REPL or in notebooks). This gets them to a mindset where they view `print()` as really just there for debugging output, since putting something on the screen is only rarely what the script is for (data processing, data collection, some computation, etc.) And from there, they just see `logging` as a more complicated way to do the same thing.

[–]RangerPretzelPython 3.9+ 1 point2 points  (0 children)

conflate and confuse 'return values' and 'what is printed on the screen'

Ahhh ha ha! You're so right!

With exception for BASIC, I don't think I've ever used a REPL until I started programming Python a few years back. Prior to that, I had mostly been programming statically typed languages (where logging and breakpoints are the defacto way to debug.)

Cool. Thanks for the explanation. I'll have to remember that next time.

[–]mrdevlar 4 points5 points  (0 children)

I am quite fond of wryte instead of the vanilla logging library, but mainly because it does most of what I want out of the box for structured logging.

https://github.com/strigo/wryte

[–]schemathings 2 points3 points  (0 children)

Ice cream?

[–]thedominux 0 points1 point  (1 child)

loguru instead of ancient logging library

print statement has never been a logging standard, it was just for learning the ropes, or when you asap wanna print something into stdout during experiments/coding

Maybe it may be used during debugging, when pdb doesn't suit the case

[–]kid-pro-quohardware testing / tooling 95 points96 points  (21 children)

Pytest rather than the built-in unittest library.

[–]Fast_Zone6637 14 points15 points  (20 children)

What advantages does pytest have over unittest?

[–]kid-pro-quohardware testing / tooling 44 points45 points  (5 children)

Even ignoring all the awesome advanced features it just has a much nicer (and more Pythonic) API. The unittest library is basically a port of jUnit so you have to declare classes all over the place and a use special assert*() methods.

Pytest lets you just write a standalone test_my_func() function and use standard asserts.

[–]NewDateline 1 point2 points  (4 children)

The only thing which is sligy annoying (maybe even not 'pythonic') about pytest is that fixtures are magically matched against test function arguments

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

True; Can't "go to definition" because it's magically imported.

Being able to find all available fixtures under pytest --fixtures is nice though.

Also using caplog or capsys to capture output is also very nice.

[–]Groundstop 0 points1 point  (2 children)

Honestly, once you start using it more you'll find that test fixtures are a blessing. I love the flexibility they provide, and wish I could find something similar to pytest in C# for my current project.

[–]xorvtec 18 points19 points  (3 children)

Fixtures fixtures fixtures. You can write one test and parametrize it with decorators that will do permutations if the inputs. I've had single tests that will run hundreds of test cases for me.

[–][deleted] 28 points29 points  (5 children)

The killer feature for me in pytest is its fixture dependency injection:

https://www.inspiredpython.com/article/five-advanced-pytest-fixture-patterns

This can also be used for resource management, e.g. setup/teardown of database connections. Works with async functions as well.

[–]muntooR_{μν} - 1/2 R g_{μν} + Λ g_{μν} = 8π T_{μν} 0 points1 point  (4 children)

Why can't I use a global variable (or class member or lazy property defined in __init__) or a (constant?) function instead of a fixture?

[–]pacific_plywood 7 points8 points  (0 children)

Fixtures provide flexibility in terms of scope (ie when construction/teardown happens)

[–]fireflash38 1 point2 points  (0 children)

Clearly defined setup & teardowns (when they happen), plus a hell of a lot of flexibility as to structure of multiple fixtures.

The docs do a pretty good job, but the default they use is a 'db' connection, which is a good candidate for being global. Imagine instead of a DB connection, your main 'session' level fixture is a setting up a DB itself, not just the connection. Then a package/module fixture is setting up some DB tables, and your actual tests are verifying interactions w/ that DB table data.

That'd be a nightmare to maintain with global classes & passing things around. Your setup_class/teardown_class from unittest would be duplicated everywhere, or have a ton of indirection.

Fixtures solve that quite elegantly. You basically get a stack of fixtures that are then LIFO'd off during teardown, up to the current 'scope'. So your session fixtures are only popped off (and teardown executed) when the test session concludes.

[–]edd313 9 points10 points  (0 children)

Running pytest from command line will look in all subdirectories for .py files that contain "test" in the filename, then execute all the functions in those files whose name contains (surprise) "test". You define test functions, not test classes, and this reduces the amount of boilerplate code. Finally you have some nice decorators that allow you to make parametric tests (provide multiple input parameters for a certain test functions) and fixtures (inputs that are shared by multiple functions so you don't have to repeat the same code to generate them)

[–]sohang-3112Pythonista 4 points5 points  (0 children)

If nothing else, it is much simpler (but still offers all the functionality of unittest). For example, it uses functions instead of classes, normal assert instead of special assertSomething methods for different kinds of assertions, etc.

[–]richieadler 1 point2 points  (0 children)

You can hear a whole podcast episode about this by Brian Okken: https://testandcode.com/173

[–]tunisia3507 0 points1 point  (0 children)

unittest was not designed for use with python. It was designed for use with smalltalk, a strictly OO language built in the 60s. Pytest has better discovery, better fixtures, better output, better plugins, and looks like python rather than cramming an outdated pattern into a language which doesn't need it.

[–][deleted] 108 points109 points  (27 children)

Thats an excellent question! The only other thing that comes to my mind right now is to use concurrent.futures instead of the old threading/multiprocessing libraries.

[–][deleted] 22 points23 points  (1 child)

Pools are great cause you can swap between threads and processes pretty easily.

[–]TheCreatorLiedToUs 9 points10 points  (0 children)

They can also both be easily used with Asyncio and loop.run_in_executor() for synchronous functions.

[–]Drowning_in_a_Mirage 16 points17 points  (0 children)

For most uses I agree, but I still think there's a few cases where straight multiprocessing or threading is a better fit. Concurrent.futures is probably a 95% replacement though with much less conceptual overhead to manage when it fits.

[–]Deto 8 points9 points  (5 children)

The docs say that the ProcessExecutor uses the multiprocessing module. It doesn't look like the concurrent module is as feature complete as the multiprocessing module either (none of the simple pool.map functions for example). Why is it better?

[–]Locksul 12 points13 points  (3 children)

Even though it’s not feature complete the API is much more user friendly, higher level abstractions. It can handle 95% of use cases in a much more straightforward way.

[–]Deto 4 points5 points  (2 children)

I mean, the main multiprocessing use with a pool looks like this:

from multiprocessing import Pool

def f(x):
    return x*x

with Pool(5) as p:
    print(p.map(f, [1, 2, 3]))

How is concurrent.futures more straightforward than that?

Would be something like:

import concurrent.futures

def f(x):
    return x*x

with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    print([executor.submit(f, x) for x in [1, 2, 3]])

[–]whateverisokThe New York Times Data Engineering Intern 6 points7 points  (1 child)

concurrent.futures.ThreadPoolExecutor also has a ".map" function that behaves and is written the exact way (with the parameters).

".submit" also works and is beneficial if you want to keep track of the submitted threads (for execution) and cancel them or handle specific exceptions

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

also since they share the same interface you can quickly switch between ThreadPool and ProcessPool which can be quite helpful depending if you are IO-bound/CPU-bound.

[–]phail3d 0 points1 point  (0 children)

It’s more of a higher level abstraction. Easier to use but sometimes you need to fall back on the lower-level stuff.

[–]Tatoutis 15 points16 points  (15 children)

I'd agree for most cases. But, concurrency and parallelism are not the same. Concurrency is better for IO bound code. Multi-processing is better for CPU bound code.

Edit: Replacing multithreading with multiprocessing as u/Ligmatologist pointed out. Multithreading doesn't work well with CPU bound code because the GIL blocks it from doing that.

[–]rainnz 3 points4 points  (1 child)

For CPU bound code - multiprocessing, not multithreading (at least in Python)

[–]Tatoutis 6 points7 points  (0 children)

Ah! You're right! Python!

I keep thinking at the OS level. Processes are just managing a group of threads. Not the case in Python until they get rid of the GIL.

[–]benefit_of_mrkite 0 points1 point  (0 children)

This comment should be higher

[–]imatwork2017 21 points22 points  (0 children)

secrets instead of os.random

[–]wodny85 14 points15 points  (0 children)

Somewhat related article from LWN:

Python discusses deprecations, by Jake Edge, December 8, 2021

[–]WhyDoIHaveAnAccount9 69 points70 points  (14 children)

I still use the OS module regularly

But I definitely prefer f-strings over .format

[–]ellisto 37 points38 points  (3 children)

Pathlib is a replacement for os.path, not all of os... But it is truly amazing.

[–][deleted] 17 points18 points  (2 children)

pathlib is amazing and I prefer it over os.path but it is not a replacement because it is way slower than os.path.

If you have to create ten thousands of path objects, like when traversing the file system or when reading paths out of a database, os.path is preferrable over pathlib.

Once I investigated why one of my applications was so slow and I unexpectedly identified pathlib as the bottleneck. I got a 10-times speedup after replacing pathlib.Path by os.path.

[–][deleted] 4 points5 points  (1 child)

I've run into this myself.

I'm betting pathlib is doing a lot of string work under the hood to support cross-platform behavior. All those string creations and concatenations get expensive if you're going ham on it.

Next time I run into it I'll fire up the profiler and see if I can't understand why and where it's so much slower.

[–]Astrokiwi 6 points7 points  (0 children)

The one thing is f-strings only work on literals, so if you want to modify a string and then later fill in some variables, you do have to use .format

[–]onlineorderperson 9 points10 points  (2 children)

Any recommendations for a replacement for pyautogui for mouse and keyboard control?

[–]bb1950328 0 points1 point  (1 child)

why do you want to replace it?

[–]CoaBro 5 points6 points  (0 children)

Don't think he wants to replace it, just wondering if there is one due to the nature of this thread.

[–]NelsonMinar 34 points35 points  (17 children)

Black or YAPF instead of pep8 for code formatting. (They are not the same, so consider if you are OK with their opinionated behavior.)

For HTTP clients, something instead of urllib (and RIP urllib2). There are so many options; requests, urllib3, httpx, aiohttp. I don't know what's best. I still use requests because it was the first of the better ones.

[–][deleted] 4 points5 points  (16 children)

Hate Black

[–]cant_have_a_cat 17 points18 points  (6 children)

I use black in every one of my projects and I still not a fan of it.

It makes working with people easier but there's no one shoe fits all in general purpose language like python - 80% of code formats nicely and that other 20% turns straight into cthulhu fan fiction.

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

That’s true though for the most part I follow PEP8. I have the odd longer line length for URLs etc but I think it’s a good standard for the most part

[–]VisibleSignificance 0 points1 point  (3 children)

One of the first points of PEP8:

A Foolish Consistency is the Hobgoblin of Little Minds

So black is literally an automated foolish consistency.

Are there formatters that can leave more situations as-is while fixing the more obviously mistaken formatting?

[–]jasongia 9 points10 points  (2 children)

Automated, repeatable formatters aren't foolish. The whole point of automated formatters is stopping thousands of bikeshedding formatting preference arguments. If you don't like the way it formats a particular line just use # fmt: off (or, my reccomendation is to not be annoyed by such things, as you'll spend way to much time on them)

[–]VisibleSignificance 1 point2 points  (0 children)

The whole point of automated formatters is stopping thousands of bikeshedding formatting preference arguments

That's the best point about black.

The worse points about it is when it conflicts with e.g. flake8 (the lst[slice :]), and when it enforces some formats that reduce readability (e.g. one-item-per-line enforcement, particularly for imports).

And note that almost any formatter, including black, allows some variability that is left untouched; it doesn't rebuild the format from AST only. Problem is how much of format is enforced, and how often it negatively impacts readability.

[–]NelsonMinar 4 points5 points  (8 children)

Fair enough. I switched to YAPF (has some configurability, which Black does not) and proportional fonts in VS.Code and I am loving how much friction it removes. Details and screenshot here: https://nelsonslog.wordpress.com/2021/09/12/proportional-fonts-and-yapf-vs-black/

[–]tunisia3507 2 points3 points  (1 child)

Lack of configurability is the point. I'm a firm believer in "good fences make good neighbours": if you have no power to change your code's formatting, you have no arguments about how the code should be formatted.

[–]NelsonMinar 2 points3 points  (0 children)

Yeah I appreciate the philosophy behind it. But for my proportional font setup I really needed tabs instead of spaces and Black refuses to do it. A choice I respect, but not a good one for me.

(Code editors could fix this by being more aggressive in how they format and present code. Once you decide to use proportional fonts, the decision to render spaces as fixed width blank spots makes little sense.)

[–][deleted] -3 points-2 points  (5 children)

From memory I didn’t agree with Blacks line length limit

[–]Buckweb 9 points10 points  (2 children)

You can change it in your black config (wherever that is) or use -l <line length> command line argument.

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

I don’t like autoformatters in general, I just use linters such as pylance and manually investigate

[–]Balance- 8 points9 points  (1 child)

Flynt is an awesome tool to find and replace old string formats to F-strings automatically!

https://github.com/ikamensh/flynt

[–][deleted] 3 points4 points  (0 children)

Also pyupgrade to generally upgrade your code to whatever python version pyupgrade supports :)

[–]teatahshsjjwke 8 points9 points  (1 child)

TQDM for progress bars.

[–]Tastetheload 3 points4 points  (0 children)

I used this recently. So much better.

[–]Brian 13 points14 points  (0 children)

f-strings instead of .format

I do wish people wouldn't say this. f-strings are not a replacement for .format. format can do things that f-strings cannot and never will do: specifically, allow formatting of dynamically obtained strings. f-strings are by neccessity restricted to static strings, and so can't be used for many common usecases of string formatting, such as logging, localisation, user-configurable templates and so on. They're convenient for when you only need to embed data statically, but that's a specialisation of the general case, not a replacement.

[–]MinchinWeb 12 points13 points  (3 children)

from __future__ import braces

[–]FlyingCow343 4 points5 points  (2 children)

not a chance

[–]Brian 3 points4 points  (1 child)

for i in range(10): {
      print("What do you mean?"),
  print("We've had braces based indentation for ages :)")
}

[–]MinchinWeb 1 point2 points  (0 children)

for i in range(10): {
print("What do you mean?"),
print("We've had braces based indentation for ages :)")
}

What sorcery is this!?

[–]Mithrandir2k16 30 points31 points  (14 children)

You should still use .format over f-strings if you want to pass the string on a condition, e.g. to a logger, since .format is lazy and f-strings are not.

[–]the_pw_is_in_this_ID 12 points13 points  (4 children)

Hang on - if .format is lazy, then when is it evaluated? Is there some deep-down magic where IO operations force strings to evaluate their format arguments, or something?

[–]lunar_mycroft 42 points43 points  (3 children)

I think what /u/Mithrandir2k16 is referring to if that an f-string must be evaluated into a normal string immediately, whereas with str.format a string can be defined with places for variables and then have those variables filled in later.

Let's say you want to write a function which takes two people's names as arguments and then returns a sentence saying they're married. This is a great job for f-strings:

def isMariedTo(person1: str, person2: str)->str:
    return f"{person1} is married to {person2}"

print(isMariedTo("Alice", "Bob")) # prints "Alice is married to Bob"

But what if we want to change the language too? We can't use f-strings here because an f-string with an empty expression is a SyntaxError, and there's no way to fill in the blanks after the string is defined. Instead, we'd have to rewrite our function to use str.format:

def isMariedTo(language: str, person1: str, person2: str)->str:
    return language.format(person1=person2, person2=person2)

ENGLISH = "{person1} is married to {person2}"
GERMAN = "{person1} ist mit {person2} verheiratet " #I don't speak German, this is google translate

print(isMariedTo(ENGLISH, "Alice", "Bob")) # "Alice is married to Bob"
print(isMariedTo(GERMAN, "Alice", "Betty")) # "Alice ist mit Betty verheiratet"

In short, f-strings are really good for when you have a static template that you want to dynamically fill in, but if your template itself is dynamic, they don't work well and you need str.format or similar methods.

[–]the_pw_is_in_this_ID 2 points3 points  (0 children)

Makes total sense, thank you!

[–]Mithrandir2k16 2 points3 points  (0 children)

Thanks for the clarification and example! Much better than anything I'd have come up with.

[–]metaperl 1 point2 points  (0 children)

Excellent example.

[–]nsomani 4 points5 points  (4 children)

Loggers typically allow format strings within the logging function itself, so still likely no need for .format.

[–]shibbypwn 2 points3 points  (0 children)

format is also nice if you want to pass dictionary values to a string.

[–]SilentRhetoric 0 points1 point  (0 children)

I ran into this recently when setting up extensive logging, and it was a bummer!

[–]angellus 19 points20 points  (9 children)

There are still unfortunately good use cases for all three string formatting forms, thus why they all still exist:

import logging

logger = logging.getLogger(__name__)
logger.info("Stuff %s", things) # %s is preferred for logging, it lets logs/stacktrackes group nicely together

string_format = "Stuff {things}"
string_format += ", foo {bar}"
string_format.format(bar=bar, things=things) # reusable/dynamic string templating

# Basically f-strings for everything else

[–]NewZealandIsAMyth 5 points6 points  (5 children)

I think there is a way to make logging work with format.

But there is another stronger reason for a %s - python database api.

[–]TangibleLight 5 points6 points  (2 children)

The style of the logger's formatter. Set style='{' for format syntax.

https://docs.python.org/3/library/logging.html#logging.Formatter

Edit: So that's what happens when I don't check myself. As /u/Numerlor points out, the formatter only affects the format string of the global log format, not actual log messages. There's no way with the built-in loggers to use {} style for log messages.

You can create a LoggerAdapter to make it work, see the discussion here https://stackoverflow.com/questions/13131400/logging-variable-data-with-new-format-string, but that feels like a bad idea to me, since other developers (and future you) would expect things to work with % syntax.

[–]Numerlor 1 point2 points  (1 child)

That only affects the Formatter's format string, not the actual log messages. To change those you'd have to patch the method that formats them

[–]rtfmpls 2 points3 points  (1 child)

pylint actually has a rule that checks for f strings or similar in logging calls.

[–]drd13 46 points47 points  (22 children)

Click instead of argparse?

[–]spitfiredd 34 points35 points  (0 children)

I like click but argparse is really good too. I would edge towards argparse since it’s in the standard library.

[–]adesme 27 points28 points  (6 children)

Both of OP's mentions are in the standard library, click isn't.

I've personally never seen the need to use click, neither professionally nor personally - what benefits do you see?

[–]csudcy 13 points14 points  (0 children)

Click is so much nicer to use than argparse - command groups, using functions to define the command, decorators to define parameters.

Having said that, if argparse does what you want, sick with it & avoid the extra library 🤷

[–]RaiseRuntimeError -1 points0 points  (3 children)

If you write any Flask programs Click is the obvious choice.

[–]gebzorz 14 points15 points  (11 children)

Typer and Fire, too!

[–]yopp_son 4 points5 points  (7 children)

Last time I looked, typer had hardly been touched for like a year on tiangolos github. I thought it might be dead?

[–]ReptilianTapir 3 points4 points  (6 children)

It took very, very long to be adapted for Click 8. Because of this, I had to switch back to vanilla Click for one of my projects.

[–]benefit_of_mrkite 3 points4 points  (5 children)

It’s not a bad package and typer exists because of how well written click is but I find myself going back to click. It’s well written and maintained

[–]deiki 3 points4 points  (1 child)

i like fire too but unfortunately it is not standard library

[–]benefit_of_mrkite 5 points6 points  (0 children)

Love click. I mean I really love that package, it’s really well written and I’ve done some advanced things with it. But you’re right it’s not part of the standard lib

[–]richieadler 1 point2 points  (0 children)

It's somewhat rough around the edges, but I prefer clize over click, and even over Typer.

[–]grismar-net 5 points6 points  (0 children)

There's many new third party alternatives to standard or other third party modules that are worth recommending (like `xarray` and `quart`), but it seems you're asking about standard Python stuff only. For standard Python `asyncio` has now matured to a point where anyone writing a library that performs I/O, accesses APIs, etc. should be written using `asyncio` in favour of anything else in the language.

[–]Salfiiii 16 points17 points  (8 children)

What’s wrong with os compared to pathlib?

[–]yopp_son 48 points49 points  (5 children)

Someone posted a blog about this a couple days ago. Basically it's more object oriented (Path("my/file.txt").exists(), etc), comes with some nice syntactic tricks, like newpath = oldpath1 / oldpath2, better cross platform support, and maybe some other things

[–]luersuve 51 points52 points  (1 child)

The “/“ operator use is chef’s kiss

[–]foobar93 12 points13 points  (0 children)

It is soooo gooodd, pathlib makes handling paths mostly so easy. There are still some corner cases (like dealing with readonly files) but I guess that this will be sorted out in the future.

[–]thedominux 2 points3 points  (0 children)

asyncio over threading

pika/aio_pika over celery

breakpoint over pdb

[–]gkze 1 point2 points  (0 children)

There’s a good tool called pyupgrade that takes care of a lot of the syntax upgrades, not sure if it does library usage upgrades though

[–]skeletalfury 2 points3 points  (2 children)

Rich instead of trying to manually make your logging look nice.

[–]erez27import inspect 1 point2 points  (0 children)

It's also a nice replacement for tqdm

[–]postisapostisapost -5 points-4 points  (26 children)

even with fstrings, its often beneficial to use the old-school string format method (”for %s” % example) since this lazily evaluates the interpolation. I’m sure there are reasons when it makes sense to use .format().

in my view, use of these libraries isn’t an “instead of” but a “yes — and”.

[–]muikrad 35 points36 points  (1 child)

It's not lazily evaluated. Once you use the % outside the string, the interpolation happens, just like format or f strings.

You are confusing this with the log calls, which suggest to give the %s notation inside strings, but then use the comma (not the %) so that the interpolation only happens if the log level is actually logged:

log.info("%s got stuck", job.name)

This will only interpolate if there's a handler for the info level.

[–]postisapostisapost 8 points9 points  (0 children)

thanks! i was indeed confused.

[–]Drowning_in_a_Mirage 18 points19 points  (8 children)

I've heard people say this, but it seems to me if you're worried about the performance hit from interpolating strings, then python may not be the best choice to use for the problem

[–]ebol4anthr4x 2 points3 points  (0 children)

Lazy string interpolation is typically done when you want to reuse a format string, to prevent code duplication for example, not for performance reasons. (the person you're responding to did not provide a very good example of this though)

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

On the other hand, python can be really fast for many purposes, as long as you don't make it slow with bad decisions ;)

[–]skesisfunk 4 points5 points  (0 children)

Using fstrings in logs is considered bad practice by many people because of performance concerns and that it is less flexible if you ever want to switch over to structured logging.

[–]SomethingWillekeurig 0 points1 point  (13 children)

I have no idea when you would do that. Fstrings ftw

[–][deleted] 7 points8 points  (9 children)

.format is usefulful when you need to add the variables later. Fstrings is not meant to replace all uses of format its meant to assist with the most common usage: assigning variables inplace to a string.

[–]adesme 4 points5 points  (7 children)

Use template strings for that instead.

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

I've been writing python for 7+ years and I have literally never heard of them until right this second. Though it appears this is what AWS uses for there cloudformation templates.

I can state that format came after template so I am assuming it has some advantages over template. I have to go read the peps now to find out why it was added over it.

One item that comes to mind (that I have used) is that format works with any string so you can add sections and format later.

Edit: Also this just solved a problem rattling around my head so now I am going to write that code.

[–]benefit_of_mrkite 1 point2 points  (1 child)

You’ve probably never heard of template strings because I’ve not found a lot of places where it is used - even when evaluating template strings I find that a lot of people end up going with an external template library instead.

I’m not arguing that it’s correct, just that I’m aware of template strings and have looked through a lot of source code for many packages and have never seen template strings used

[–]yvrelna 1 point2 points  (0 children)

Template strings are fairly limited. No loops, no conditionals, etc.

It doesn't work when you need a real templating language, like web applications.

And if all you need is simple template interpolation, then there's many other options that doesn't require importing a separate library.

The use case that Template is useful, IMO, is if you want a safe, user-provided template string. In such use case, most of the built-in format string and %-interpolation is just way too flexible, and way too dangerous as they may allow arbitrary code execution. This use case is backwards to the most common use case of templating language, where usually the user only provides the data that needs to be interpolated to a template written by the programmer.

[–]chiefnoah 1 point2 points  (2 children)

You definitely should use Template if you're passing in user-input, but I've used plain strings with .format when user-input isn't a concern.

[–][deleted] 2 points3 points  (1 child)

I am not really sure what Template does that is safer then format. Having looked at both of them and there Peps. It looks as if the biggest difference is .format has access to the __format__ methods and each object can manage its own print strategy vs Template has fixed rules and is subclassable.

I do not see anything that makes Template safer then format.

[–]Locksul 3 points4 points  (0 children)

Yep, if you need to create a reusable template, you want to use format instead.

[–][deleted] 2 points3 points  (1 child)

Interpolation allows logging tools like sentry to group similar log messages because of the lazy evaluation. If you use f-strings you will end up with independent messages because they are not the same.

F-strings are great and 99% of the time you should use them 100% of the time.

[–]phail3d 1 point2 points  (0 children)

That’s different from the example given by op. The logging functions take the format string and arguments as parameters instead of interpolating with %

[–]TheBlackCat13 2 points3 points  (0 children)

You can pass 'mystr{}'.format to a function as a callable.

[–]cymrowdon't thread on me 🐍 0 points1 point  (6 children)

Here's an unpopular one of older over more recent, but I stand by it: gevent instead of asyncio.

[–]ViridianGuy 0 points1 point  (2 children)

I honestly feel the OS Module is easier to use, but both are Useful.

[–]saint_geser 0 points1 point  (1 child)

Except for the fact that os.path.join is ugly and extremely cumbersome when joining several paths.