Bind built-in functions to a class

If a function is assigned as a class variable, it will behave like a method (with instances bound as first argument). However, that does not work with buit-in (native) functions:

def say(*args):

class Foo:
    say = say
    print = print
>>> Foo().say("bar")
<__main__.Foo instance on 0x...> bar
>>> Foo().print("bar")

and indeed

>>> Foo().say
<bound method say of <__main__.Foo object at 0x...>>
>>> Foo().print
<built-in function print>

A few questions:

  • Is this behavior documented somewhere?
  • Do you have a solution in mind to make that work — other than the explicit, but quite redundant def print(self, *args): print(self, *args)? I read elsewhere that func = classmethod(func) do the tricks, but we don’t have instancemethod decorator, obviously :smile:
  • Would you considered that “trick” a bad code practice?

I personaly use this times to times, often with one-args functions like copy.copy, so I can do foo.copy() instead of copy.copy(foo). Never noticed it doesn’t work with built-in functions until today!

I think you can easily verify that that doesn’t (generally) work (for instance it doesn’t work with ‘sum’) and it may not be what you want even when the function calls do work (for instance with print, you’d always also be printing the redundant repr of the Foo class).

Good or bad coding practice is of course pretty subjective. Personally I would not use this whole pattern (and also not the trick) and I’d tend to reject any PR that used this pattern, since it sort of seems to obfuscate what is happening. So, unless there is a strong, external reason to do so… (Simply convenience would not count :slight_smile: ) I mean - you’re adding an extra layer of indirection, an extra namespace, but without any inherent need and without big “ergonomic” benefits either. Right?

class Foo:
    print = classmethod(print)
    sum = classmethod(sum)
>>> Foo().print("bar")
<class '__main__.Bar'> bar
>>> Foo().sum()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'type' object is not iterable

I’d say it works pretty well (by binding Foo as the first argument of the function), but indeed that’s not what I want here :grin: just mentioned it as an existing “one-liner trick” for a similar problem.

I guess there isn’t, really. (maybe a few ns of speedup, as it skips one function call?)
If the function signature was more complicated, one could argue it avoids duplicating it, but that’s not my use case.

In the section Instance Objects there is the sentence

By definition, all attributes of a class that are function objects define corresponding methods of its instances.

It does not explicitly exclude the complement. Namely, that attributes that are not function objects don’t define methods, but I suppose it was also intended.

So, since the value assigned to Foo.say is of class <class 'function'>, this turns into a method

>>> Foo().say.__class__
<class 'method'>

and the value assigned to Foo.print being of class <class 'builtin_function_or_method'>, makes it remain an attribute which value has class

>>> Foo().print.__class__
<class 'builtin_function_or_method'>

Not explicitly/directly, insofar as “function object” (per what @Franklinvp turned up) could reasonably be read to include builtins. However, it follows naturally from the fact that they lack a __get__ attribute, which is what implements the method-binding magic (which is documented).

As you’ve apparently noticed, classmethod will bind the class, rather than an instance. You can streamline the binding logic: lambda produces ordinary function objects (__get__ and all; they just have a special __name__), and the self parameter doesn’t need to be called out specially (unless you are explicitly trying to prevent Foo.print() from successfully printing just a blank line). On the other hand, it would probably be a good idea to bind keyword arguments as well. Thus:

class Foo:
    print = lambda *p, **k: print(*p, **k)

The sort of use you have in mind is a bit abusive: the point of instance methods is that they expect the first argument to receive specifically an instance of your type as the first argument, but those builtins were written without any knowledge or forethought about the existence of your class.

In general, being able to assign methods like that is useful (for example, I’ve done things like __rmul__ = __mul__ before) but I can’t see a good use case that involves builtins. If you’re doing it just in order to have method-call syntax for an ordinary task then yeah, I would strongly discourage that. Well, I guess it’s fine for .copy, since lists and dicts do the equivalent, and it saves an otherwise annoying import for the client. But keep in mind the syntax that readers of the code are expecting to see. There are well-considered design reasons why dunders exist and we don’t use method syntax for everything.

I’ve said it many times before: object-oriented programming is not about classes. If it were about classes, we would call it class-oriented programming instead. A consequence of that is that, sometimes, the right way to interact with an object is not its methods. (Ironically, one of the best arguments for this point that I’ve seen - note that the link does not support HTTPS - comes from a C++ guy.)