Lisps like Scheme do indeed have an easier time with these due to the whole code-is-data thing, it's quite doable in languages with real syntax though. R and Julia would be good examples of syntactic languages with full macros. The Julia implementation might be a good model for what Python could do.
Their docs are also a nice read if anyone isn't familiar with the topic.
Macropy represents unevaluated expressions with the objects from the ast module. This seems like a sane choice.
To be a little pedantic I'll give a brief example loosely showing what a macro is, then I'll talk about assert statements as a use case where macros might help with a pain point in normal Python programming.
Brief Educational Blurb
We write code as text
defmacro f(x):
...
f(a + b) * sin(c)
We then parse parts of that text into syntax trees.
Usually we translate these trees into byte-code and evaluate bottom-up, starting with a + b, then applying f, etc... Macros stop this process. They capture the subtrees beneath them before execution. Whenever we see a macro (f), we don't evaluate its subtree (a + b). Instead we transform the subtree into an in-memory representation (perhaps ast.BinOp(a, ast.Add(), b)) and hand that to f to do with as it will. Lets see an example with assertions.
Use case with Assertions
When testing we often want to write statements like the following
assert x == y
assert x in y
etc...
When these statements fail we want to emit statements that are well informed of the full expression, e.g.
5 != 6
5 was not found in {1, 2, 3}
In Python we can't do this; assert only gets True or False and doesn't understand what generated that value . We've come up with a couple of workarounds. The first is the venerable unittest.TestCase methods that take the two sides of the comparison explicitly e.g. assertEquals(a, b), assertContains(a, b). This was sufficiently uncomfortable that projects like py.test arose and gained adoption. Py.test goes through the trouble of parsing the python test_.py files in order to generate nicer error messages.
Having macros around would allow users to write this kind of functionality directly in Python rather than resorting to full text parsing and code transformation. Macros provide an escape out of pure bottom-up evaluation.