0

Consider the following case:

class test:
  def foo(self, o):
    print(o)

  @staticmethod
  def bar(o):
    print(o)
    
  @classmethod
  def qux(cls, o):
    print(cls)
    print(o)

def baz(o):
  print(o)

t = test()

class A:
  meth1 = t.bar
  meth2 = t.foo
  meth3 = baz
  meth4 = t.qux

a = A()
a.meth1()
a.meth3()
# a.meth4()
# a.meth2()

This works just fine, but if I call meth2/4 I get the following error:

TypeError: <foo/qux>() missing 1 required positional argument: 'o'

Is there any way I can get t.foo and t.qux working like t.bar and baz?

ntjess
  • 570
  • 6
  • 10

1 Answers1

0

meth4 in A isn't being treated as a class method here because you have not defined it as such.

Class methods are declared using decorator syntax, like so:

@decorator
def func(x, y):
    return z

which essentially defines a function func and then calls decorator on it.

Thus, you can actually just do meth4 = classmethod(t.qux), and it works perfectly fine.

For meth2, I'm not sure what you expected to happen. It's an instance method that takes self implicitly and takes an argument o, so when you call a.meth2, of course you need to specify an argument o.

hyper-neutrino
  • 5,272
  • 2
  • 29
  • 50
  • I expect `self` to be bound to `t`, and when `a.meth2` is called, it would bind `a` to `o`. I am basically looking for some way to automatically bind instance methods to the missing function parameter – ntjess Jul 07 '21 at 04:30
  • @ntjess you could do `def meth2(self): t.foo(self)`: [Try it online!](https://tio.run/##ZY9NDoIwEIX3nGKWbWKaCKxMTOQoFdpIghTpkKiXrzOlVsXde29@vpnpgRc3ViG0g/Ye0Hg8FACdsWCdE94MdgdOcgYwzf2IwsmC3Mmjxr69Gprv0sRZz@Kvlw33R8BP@225i3bw2/0UyQ1uXf5Myz85wjGeLEivDzTcwJQ9lxRdlGCclfGfBEP1flCmkYpGiJJcTe7rZoGK7iWOprwhoFYRk1WVVZ1VSSqEFw "Python 3 – Try It Online") – hyper-neutrino Jul 07 '21 at 04:31
  • Right, thanks. I was more curious about what makes all the other methods "auto-bind" where that doesn't happen here. I.e., what mechanism within Python causes `a` _not_ to be bound to `meth2`/`meth4`? – ntjess Jul 07 '21 at 04:38
  • @ntjess This is part of the descriptor protocol, see [functions and methods](https://docs.python.org/3/howto/descriptor.html#functions-and-methods) in the documentation. Related answers [here](https://stackoverflow.com/a/1015405/), [here](https://stackoverflow.com/a/11950080/), and [here](https://stackoverflow.com/a/49553252/). The key being in Python 3, `function` types get this treatment automatically, while other callable types (produced from `staticmethod`, `classmethod`, or simply callable class instances or classes) don't. – metatoaster Jul 07 '21 at 04:48
  • @ntjess The other ones don't actually bind. `meth1` works because calling `test.bar` on `a` sets the first argument, `o`, to `a`, because `a.b()` is just syntax for `type(a).b(a)`. `meth3` works for the same reason; calling `a.b()` just calls that function `b` but with the first argument being `a`, hence why instance methods usually call their first argument `self`. – hyper-neutrino Jul 07 '21 at 04:48
  • @hyper-neutrino interesting, thanks for taking the time to explain. I was hoping that binding occurred on the already-bound version, not on the raw function. @metatoaster Does this mean I can add my own descriptor protocol to `test` for the desired result? – ntjess Jul 07 '21 at 04:52
  • @metatoaster I am familiar with the difference between bound and unbound methods, but my mistake was thinking that function binding occurred somewhat like `functools.partial`. Thanks for the links – ntjess Jul 07 '21 at 04:56