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):
print(*args)
class Foo:
say = say
print = print
>>> Foo().say("bar")
<__main__.Foo instance on 0x...> bar
>>> Foo().print("bar")
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
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 ) 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 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.
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
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 isdocumented).
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.)