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

Macros extend the power of the language way beyond its core primitives. For instance, I wrote a macro, TEMPORARY-ASSIGN. I use it like this:

  (TEMPORARY-ASSIGN ((traversing obj) true)
     ... do stuff ...)
In Python, the equivalent code would be.

   old_trav = obj.traversing
   obj.traversing = True
   try:
      ... do stuff...
   finally:
      obj.traversing = old_trav
There's no way to abstract out that pattern in Python. Every time you want to temporarily assign a field or variable, you're stuck writing the above code. Another example:

   (defun foo (x y) ...)
is how you define a function in Common Lisp. I wrote a macro, DEFUN-CACHE

   (defun-cache foo (x y) ...)
which is the cached version. In Python, you can do the same with decorators, but that's one more tacked-on feature. Lisp programmers have been writing defun-cache since 40 years.

If you want to learn more, Paul Graham's On Lisp is the definitive book on the topic. You can download it for free http://www.paulgraham.com/onlisp.html, and it's very readable, even if you're not a Lisper.



> There's no way to abstract out that pattern in Python.

I'm sure there are macros that can't be abstracted out in Python but this isn't one of them:

    from contextlib import contextmanager
    
    @contextmanager
    def temp_assign(obj, attr, val):
        old_val = getattr(obj, attr)
        setattr(obj, attr, val)
        yield
        setattr(obj, attr, old_val)
    
    class X:
        pass
    
    x = X()
    x.a = 1
    with temp_assign(x, "a", 2):
       print x.a # prints 2
    print x.a # prints 1


I think it's an extraordinary strength of Python that I hadn't seen your code when writing mine but that other than two variable names they're identical. Leaving my comment up for demonstration of this.


Ha, awesome! I went for an exact transliteration although if I were to use this idea for real I would probably do the assignment explicitly in the body. I think this looks a bit more pythonic:

    @contextmanager
    def restoring(obj, attr):
        old_val = getattr(obj, attr)
        yield
        setattr(obj, attr, old_val)
    
    x.a = 1    
    with restoring(x, "a"):
       print x.a
       x.a = 2
       print x.a
    print x.a


Cool, I didn't know that could be done. Can it work for global and local variables as well?


You can do anything you want with a context manager, it's just Python. IIRC, they were first added to the language to get rid of boilerplate while acquiring/releasing locks to make multi-threading easier.


I think I don't understand what your macro does, because the Python code is easily abstractable with a context manager:

  from contextlib import contextmanager

  @contextmanager
  def traverse(obj, attr, val):
    cached = getattr(obj, attr)
    setattr(obj, attr, val)
    yield
    setattr(obj, attr, cached)

  with traverse(obj, "traversing", True):
    # ... do stuff ...
Could you explain what your example does that I've missed?


You're right that his example is possible in Python, but that's only because with happens to be part of the language. If it were not, you couldn't write my_with in Python code alone.

Meaning that the next time you need a feature that doesn't exist in Python, you can't add it.

You should cut these examples some slack; in reality, it's going to be hard to come up with a five line Python example that's ugly, because Python is quite a nice language, and most rough edges have been sanded down over the last 20 years.

That doesn't mean the techniques aren't useful in real world programs, like when you need to build a DSL - just that they're hard to explain in a dozen line HN comment.




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

Search: