Hacker News new | ask | show | jobs
by qsort 1057 days ago
Just 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)

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
2 comments

Thanks 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

I 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?

If 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.

I 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.
Thanks 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.