53 comments

[ 10.1 ms ] story [ 1652 ms ] thread
(comment deleted)
I use icecream [1] for the same purpose while debugging. It's really handy.

[1] https://github.com/gruns/icecream

Thanks for sharing! I like that ic includes a hook for logging, which I think is an advantage over calling print directly. I realize for a lot of examples, they’re quick and dirty but I almost always end up setting up a logger with an stderr or stdout handler so that other handlers can also send it to a file if desired. This makes it easier to run these in production on daemons and such where your code might not have an stdout to print to.
Title should definitely have `dbg!` in it rather than "Dbg"
Probably was submitted with the correct title and then rewritten by the HN title modification ruleset.

I think submitters are able to correct the title for a while and that when they do it might(?) override some or all of the rules in the filter. But the time window for doing so has probably been passed now.

Mods could still change the title though.

Either to the original:

Implementing Rust's dbg! in Python

Or if they want to help out those unfamiliar with Rust syntax they might make the title be:

Implementing Rust's dbg! macro in Python

And if they are opposed to use the bang even in this context it would then be fine IMO to either preferably set the title to:

Implementing Rust's dbg macro in Python

Or less preferably to set the title to:

Implementing Rust's debug macro in Python

Rust's dbg! is one of those ideas that felt obvious and beautiful once I saw it.

Apparently it's inspired by Haskell.[0] I'd love to see it officially picked up in other languages.

[0] https://rust-lang.github.io/rfcs/2361-dbg-macro.html#prior-a...

I've been using it in C/C++ projects going back at least 15 years, I thought it was commonplace for every language
Do you have an example of using it in C or C++?
There's a few significant differences here. This is a printf that only happens in debug mode; dbg is something that takes an expression, returns that expression, and will print out its filename, line number, and a pretty-printed version of the value.

For an example of how this plays out, I had some code:

    let x = y + z;
I wasn't sure what y was, so I was able to

    let x = dbg!(y) + z;
this is much easier to insert into (and remove from) existing code. Plus, you don't need to write the formatting code.

Basically, what you posted is in the same genre of thing, but is very different in a lot of meaningful ways.

You can use dbg inline within an expression!? How did I miss that?
You can even use it inside itself. I've written

    let c = dbg!(dbg!(a) + dbg!(b));
one time...
I've written a 3 nested dbg! line for a complex boolean expression... I should have just written a truth table...
Maybe you should write a truth table/source code macro!
After I saw this in Rust, I had to implement it in C++:

    #include <iostream>
    #include <string>

    template<typename T>
    T print_dbg(T val, std::string exp, std::string file, int line) 
    {
        std::cerr << exp << " = " << val << " @ " << file << ":" << line << "\n";
        return val;
    }

    #define dbg(x) print_dbg(x,#x,__FILE__,__LINE__)
Close. Applying that to

    double c = dbg(dbg(a) + dbg(b));
gives

    a = 1 @ debug.cxx:16
    b = 2 @ debug.cxx:16
    dbg(a) + dbg(b) = 3 @ debug.cxx:16
which has extra dbg()s on the third line.

If I could I would probably want to write

    double dbg(c) = dbg(a) + dbg(b);
but that of course doesn't work.
Like Steve said, what you're talking about is quite a bit different than what the rust dbg macro does.
It isn't really that different, it can just be used in inline expressions. Having the macro do nothing or just evaluating to the expression in debug mode is trivial, since a debug symbol would be defined.
(comment deleted)
Python now as a semi-shortcut for this (just add = to the end of any f-string expression):

In [8]: print(f'{foo.bar[1]=}') foo.bar[1]='2'

You can do more complex expressions:

In [9]: f'{1+1=}' Out[9]: '1+1=2'

It's not quite as fluid as dbg!(), but it's nice.

For anyone else wondering why we get a syntax error, this seems to be new in 3.8. :)
So anyone using Ubuntu or Debian, maybe for the next major release you can try it out. I love Debian but I hate that I cant just install the latest of Python system wide without potentially breaking system level scripts.
Just use pyenv (careful with spelling), then you can use any version you like.
Definitely will have to check it out with two endorsements, I've heard of a few other options, but this might be something I'm actually okay with.
If you don't want to use pyenv (I think it's a mess), just look into compiling Python yourself, using a prefix like ~/.local/share/python-$version
Might be worth mentioning that you do not have to resort to printf debugging in Rust or python; both languages have good ide debugger support. I use python and rust together professionally.
For something quick, it's often more ergonomic to just print a thing out than step through. Also, most debuggers also won't be very helpful for embedded expressions; you have to break it out into a local variable.
You can set a breakpoint and also watch expressions, so there is not more work involved than hitting 2 keyboard shortcuts usually.
(comment deleted)
printf debugging (i.e. listing code execution traces) is better than a breakpoint-based debugger in most situations, in my opinion.
For what it's worth, I made a Python package that lets you combine both approaches: https://github.com/breuleux/breakword. You run your program once to print out what's going on, but it also displays a unique word next to each line and on a second run you can set a breakpoint at any of these words to investigate deeper. It's pretty handy when you spot an issue at the hundredth invocation of some function deep in the program's execution.
That's really awesome.
They do, but sometimes you have to debug in an environment where an IDE is not available, and even remote attach can be problematic sometimes.

And then, sometimes, you need to debug the debugger...

The one thing I wasn't sure that I liked with rust's dbg! is that I was typically passing the argument by value, but that sometimes works and sometimes fails the borrow checker. But now reading https://github.com/rust-lang/rust/issues/59943 I see that this is a feature -- I hadn't been taking advantage of the fact that you can use dbg! inline; I'd just been adding extra dbg!(foo) lines to the code. So I should probably (a) pass by reference always and (b) look our for where inline dbg! might be useful (probably lots).
I created a similar macro in C++, it had the ability to show the function name in addition to the file name
But did it return the value being printed? That's the thing most implementations miss.
Oh never thought about that use case but I imagine it would have been simple to add
I dont get, isn't this just grabbing the filename (which has a built-in double underscore file variable), and the line number (e.g. https://code.activestate.com/recipes/145297-grabbing-the-cur...)

not to mention you can run via pdb and display or stop at points of interest.

what am I missing?

dbg! also prints out both the value of the expression, and the expression itself, so dbg!(x+y) prints 'x+y=3'
While I realize we don't always have a logger wired up, it's also easy to implement this in standard logging.

%(filename_lineno)s will give you the filename and line.

Loguru is a great package for "I just want a nicely formatted logger out of the box"
Typo in your first eval example:

    >>> foo = {
    ...     'bar': 3
    ... }
    >>> print(eval("foo['bar'[]"))
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<string>", line 1
        foo['bar'[]
                  ^
    SyntaxError: unexpected EOF while parsing
I think that's the extra opening square bracket.

Should be: print(eval("foo['bar']"))

My personal experience is that doing println/dbg based debugging in python is most times a bad idea and does wast time.

Ironically my experience with rust is the opposite, i.e. not using println/dbg debugging is most times does wast time. (Except if you do certain thinks with unsafe code).

The difference is I guess that in rust I normally encode much more simple invariant in the type system and as such most times a bug happens I often can pin-down it's rough position by the way it appears (i.e. panics/errors not subtle timing bugs). In turn I just need to get a nice formatted printout of this one or two values. Instead of having to consider all the other ways things might have cone wrong in python which can't happen in rust (through python3's type annotation + linting can help).