Python class methods: when you don't need it yourself
I am trying to rewrite some code using classes. At some point I want to assign a member function to a specific definition using the parameter value for each object instance.
Coming from other languages ββ(JavaScript, C ++, Haskell, Fortran, ...) I am struggling to understand some things in Python. It is one thing to differentiate between "I" in class methods.
For example, the following code will obviously not work:
class fdf:
def f(x):
return 666
class gdg(fdf):
def sq():
return 7*7
hg = gdg()
hf = fdf()
print(hf.f(),hg.f(),hg.sq())
which gives the error that "sq () takes 0 positional arguments, but 1 is given".
The reason, in my opinion, is that during the execution of the function, a reference to the caller (the instance calling sq) is passed as the first argument before any other parameter / argument that we might have defined / named sq with. So the solution is simple: change the sq code to def sq(self):
. Indeed, the Python 1 tutorial seems to suggest that object methods should always be defined with self
as the first parameter. We do as expected 666 666 49
. So far so good.
However , when I try to implement my class like this:
class Activation:
def nonLinearBipolarStep(self,x,string=None):
if not string: return (-1 if x<0 else 1 )
else: return ('-' if x<0 else '1')
default='bipolar'
activationFunctions = {
'bipolar': nonLinearBipolarStep ,
}
def _getActivation(self,func=default):
return self.activationFunctions.get(func,self.activationFunctions.get(self.default))
def __init__(self,func=None):
if func == None: func=self.default
self.run = self._getActivation(func)
ag = Activation()
print(ag.run(4))
I am getting the error
nonLinearBipolarStep() missing 1 required positional argument: 'x'
However, the workaround (solution?) Defines a step function without a parameter self
(!) As
def nonLinearBipolarStep(x,string=None):
Then I get the expected behavior (at least for this trivial test) 1
. Thus, not only is self
it unnecessary here, but even here it is used incorrectly!
But according to the tutorial mentioned above or answers in threads like this 2 or this 3 , it seems to me that this code shouldn't work ... or should have some unexpected consequences at some point (?). Indeed, if I remove all references to self
in the definition _getActivation
, I get an error _getActivation() takes from 0 to 1 positional arguments but 2 were given
that I can understand according to this rule.
Thread "Why is not itself used in this method 4 does not provide a clear answer to me: what is the syntactic detail of the code above that tells me what is self
not needed? For example, how does this code differ from this example tutorial
class MyClass:
"""A simple example class"""
i = 12345
def f(self):
return 'hello world'
? Instantiating this class works as expected, but it complains about a missing parameter (I know it could be any label) if defined with nothing.
This makes me doubt that my code is not hiding a time bomb: self
passed as the value for x
? It works as expected, so I would say no, but then I ran into this puzzle.
I guess I am missing some key ideas of the language. I admit that I am also struggling with the question that OP references 3 .
[^]: In JS, one uses this
functions in the body, and the function itself is defined as either a member of the object's prototype or an instance member, which is then assigned correctly using ... this
,
EDIT:
The stream is long. For those looking at any help, if you are new to Python, you can check the selected solution and its comments. However, if you already know about bound / unbound methods in Python, you just want to directly check the use of the handle, as described in Blckknght's answer. Ultimately, I decided to use this code in my code, using it __get__
in an assignment to run.
source to share
What is it self
?
In Python, every normal method is forced to take a parameter, usually called self
. This is an instance of a class - an object. This is how Python methods interact with class state.
You are allowed to rename this parameter however you like. but will always have the same meaning:
>>> class Class:
def method(foo): #
print(foo)
>>> cls = Class()
>>> cls.method()
<__main__.F object at 0x03E41D90>
>>>
But then why does my example work?
However, what you are probably confusing is how this code works differently:
>>> class Class:
def method(foo):
print(foo)
methods = {'method': method}
def __init__(self):
self.run = self.methods['method']
>>> cls = Class()
>>> cls.run(3)
3
>>>
This is due to the difference between bound and unbound methods in Python.
When we do this in __init__()
:
self.run = self.methods['method']
We are referring to the unbound method method
. This means that our reference to is method
not associated with any particular instance Class
, and thus Python will not force it to method
accept an object instance. because he has nothing to give.
The above code will be the same as doing so:
>>> class Class:
def method(foo):
print(foo)
>>> Class.method(3)
3
>>>
In both examples, we are calling a method method
of the class object Class
, not an instance of the object Class
.
We can see this difference by looking repr
for a bound and unbound method:
>>> class Class:
def method(foo):
print(foo)
>>> Class.method
<function Class.method at 0x03E43D68>
>>> cls = Class()
>>> cls.method
<bound method Class.method of <__main__.Class object at 0x03BD2FB0>>
>>>
As you can see, in the first example, when we do Class.method
, Python shows:
<function Class.method at 0x03E43D68>
. I lied to you a little. When we have an unbound class method, Python treats them as simple functions. Thus, method
it is just a function that is not tied to any instance of the class.
However, in the second example, when we create an instance Class
, and then turn to its object method
, we see printed: <bound method Class.method of <__main__.Class object at 0x03BD2FB0>>
.
The key point for notification is bound method Class.method
. This means it is method
bound to a cls
- specfic instance Class
.
General remarks
As @jonshapre mentioned, writing code like in your example leads to confusion (as proof on the matter) and errors. It would be better if you just define nonLinearBipolarStep()
outside Activation
and specify that from inside Activation.activation_functions
:
def nonLinearBipolarStep(self,x,string=None):
if not string: return (-1 if x<0 else 1 )
else: return ('-' if x<0 else '1')
class Activation:
activation_functions = {
'bipolar': nonLinearBipolarStep,
}
...
I guess the more specific question is: what should I pay attention to this code to make it obvious what
ag.run(x)
would be an unbound function call?
If you do want to nonLinearBipolarStep
be unrelated, I recommend just being careful. If you think your method will do for the cleanest code, then go for it, but make sure you know what you are doing and the behavior of your code will be.
If you still want users of your class to understand what is ag.run()
going to be static, you can document it in a docstring somewhere, but this is something that the user really doesn't even have to worry about.
source to share
I think what confuses you here is that you are accessing this method using the class attribute activationFunctions
and not (as usual for accessing an instance) on the instance itself. For example given:
class Class:
def method(self, foo, bar):
print(self, foo, bar)
methods = {'method': method}
When we call the method directly from the dictionary:
>>> Class.methods['method'](1, 2, 3)
1 2 3
You can see what we are passing 1
as a parameter self
; the method is not called on an instance, so no instance is injected. In contrast, when we call it an instance:
>>> instance = Class()
>>> instance.method(1, 2)
<__main__.Class object at 0x...> 1 2
Now our arguments are foo
and bar
, and the instance self
. This is why you think multiple parameters are required.
In this case, since you don't really need the instance state in your method, just make it a regular function (note the minor changes for PEP-8 ):
def non_linear_bipolar_step(x, string=None):
if string is not None:
return -1 if x < 0 else 1
return '-' if x < 0 else '1'
class Activation:
activation_functions = {
'bipolar': non_linear_bipolar_step,
}
...
This is probably less confusing.
source to share
This code uses an unbound method (nonLinearBipolarStep):
activationFunctions = { 'bipolar': nonLinearBipolarStep , }
Longer answer: Methods are functions defined in the body class and always take at least one argument, so called self (unless you use @staticfunction and turn them into regular functions). Self is an object of this class on which the method is called (for example, in C ++). In python, there is almost nothing special about this argument, it doesn't need to be called yourself. Now, when you call an unbound method, the first argument you provide will be interpreted as "me" and consumed. If you call bound methods, then this consumption does not occur (the method already has its own object). For example:
class A:
def foo(self, x): print(x)
a = A()
a.foo(1) # a.foo is bound method, a is self, prints 1
A.foo(a, 2) # A.foo is unbound method, first argument becomes self, prints 2
UPDATE: Why does this work at all. Short answer: because the dot (.) Operator will update an unbound method to bind when it can.
Consider what happens when you write a.foo (1). First python check object a for foo and finds nothing (foo is not the value assigned by a). So it goes to the class (named A) and lo and behold - foo is there and used. But here's the trick. Python will bind object a to an unbound method A.foo (details are escaping me for now, so imagine the dragon did it) and turn it into a bound method. This way a.foo is bound and no longer needs itself from the arguments, so the 1 goes into the x argument and everything works.
Now to your code: you are using "bipolar": nonLinearBipolarStep on the map, which is an unbound method. Then in the constructor ( init ) you set self.run to the value returned from _getActivation, which is taken from the activFunctions map. In this example, you return the nonLinearBipolarStep unbound method and assign it to self.run. Now you call ag.run. Following the logic from the previous paragraph, ag.run is first viewed inside the ag object. And here is your mistake - it was found. Since python discovered the value of ag.run inside the ag object, it never accessed the ag (Activation) type for the run object, and never had a chance to bind it. So ag.run is an unbound method and expects the first argument to be the first.
You have basically two options. Either make ag.run (ag, 4), which will work but its ugly, or manually bind the method to self in the constructor. The last you can do like this:
self.run = self._getActivation(func).__get__(self)
source to share
You are using one of the more subtle parts of a Python method implementation. It boils down to how the argument is bound self
for regular method calls (like some_instance.method()
). It uses the "descriptor" protocol, which is not very well documented (at least it did not become obvious to new Python programmers).
A descriptor is an object that has a method __get__
(and an optional method __set__
and / or __delete__
, but I'll only talk about __get__
here). When such an object is stored in a class variable, Python will call its method __get__
whenever the corresponding name is looked up on the instance. Note that this special behavior does not occur for descriptor objects stored in instance variables, only those that are class variables.
Functions are descriptors. This means that when you save a function as a class variable, its method __get__
is called when you view the instance. This method will return a "bound method" object that will automatically pass the argument to the self
function.
If you store the function somewhere other than a top-level class variable (such as a dictionary or an instance variable), you won't get this binding behavior, since the handle protocol will not be called if the object is viewed. This usually means that you either have to pass it self
manually or you must omit the argument self
from the function definition in the first place (in which case I would suggest moving the function out of the class so that it is cleaned up not intended to be used as a method).
But you can also create bound methods manually if you like. The type is displayed in the module types
as types.MethodType
. This way you can change your code like this and it should work:
def __init__(self,func=None):
if func == None: func=self.default
self.run = types.MethodType(self._getActivation(func), self) # be sure to import types
source to share