Python class methods: when is self not needed
我试图用类重写一些代码。在某个时刻,我想要的是使用对象的每个实例的参数值为成员函数分配一个特定的定义。
来自其他语言(JavaScript、C++、Haskell、Fortran……),我很难理解Python上的一些事情。一件事是在类方法中对self的以下区分。
例如,以下代码显然不起作用:
1 2 3 4 5 6 7 8 9 10 11 | 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()) |
错误是"sq()接受0个位置参数,但给出了1"。
据我所知,原因是在执行时,函数作为第一个参数传递给调用对象(调用sq的实例)的引用,而不是我们定义/调用sq的任何其他参数/参数。所以解决方案很简单:将sq的代码改为
但是,当我尝试像这样实现我的类时:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 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)) |
我得到错误
1 | nonLinearBipolarStep() missing 1 required positional argument: 'x' |
然而,一个解决方法(解决方案??)正在定义没有参数
1 | def nonLinearBipolarStep(x,string=None): |
然后我得到了
但是根据上面提到的教程,或者像2或3这样的线程中的答案,在我看来,这段代码不应该工作……或者在某个时刻会有一些意想不到的结果(?).事实上,如果我删除了
线程"why is self not used in this method"4没有给我一个明确的答案:上面代码的语法细节告诉我不需要
1 2 3 4 5 6 | class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world' |
?实例化这个类可以如预期的那样工作,但是它抱怨如果用none定义的话缺少参数(我知道它可以是任何标签)。
这让我怀疑我的代码是否以某种方式隐藏了一个定时炸弹:是否将
我想我遗漏了一些关于语言的关键概念。我承认,我也很难回答参考文献3中的op所问的问题。
[^]:在JS中,只在函数体中使用
编辑:这线很长。对于那些浏览以寻求帮助的用户,如果您对Python不熟悉,那么您可能需要检查所选解决方案及其注释。但是,如果您已经了解了Python中绑定/未绑定的方法,那么您只需要直接检查描述符的使用情况,如blckknght的答案中所述。我最终在代码中选择了这种方式,在运行分配中使用
您遇到了Python方法实现中更微妙的部分之一。归根结底,这取决于普通方法调用(如
描述符是一个对象,它有一个
函数是描述符。这意味着当您将函数保存为类变量时,当您在实例上查找它时,将调用它的
如果将函数存储在顶级类变量之外的其他地方(例如在字典或实例变量中),则不会获得这种绑定行为,因为在查找对象时不会调用描述符协议。这通常意味着您要么需要手动传递
但如果您愿意,也可以手工构造绑定方法。该类型在
1 2 3 | def __init__(self,func=None): if func == None: func=self.default self.run = types.MethodType(self._getActivation(func), self) # be sure to import types |
什么是
在Python中,每个普通方法都必须接受一个通常名为
您可以随意重命名此参数。但它始终具有相同的值:
1 2 3 4 5 6 7 8 9 | >>> class Class: def method(foo): # print(foo) >>> cls = Class() >>> cls.method() <__main__.F object at 0x03E41D90> >>> |
但是为什么我的例子有效呢?
但是,您可能会困惑的是,此代码的工作方式不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | >>> class Class: def method(foo): print(foo) methods = {'method': method} def __init__(self): self.run = self.methods['method'] >>> cls = Class() >>> cls.run(3) 3 >>> |
这是因为Python中绑定方法和未绑定方法之间的区别。
当我们在
1 | self.run = self.methods['method'] |
我们指的是未绑定方法
上述代码与执行此操作相同:
1 2 3 4 5 6 7 8 | >>> class Class: def method(foo): print(foo) >>> Class.method(3) 3 >>> |
在这两个例子中,我们调用类对象
我们可以通过检查
1 2 3 4 5 6 7 8 9 10 11 | >>> 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>> >>> |
如您所见,在第一个示例中,当我们执行
但是在第二个例子中,当我们创建一个
需要注意的关键部分是
正如@jonshapre所提到的,像在示例中那样编写代码会导致混淆(作为这个问题的证据)和错误。如果您简单地定义
1 2 3 4 5 6 7 8 9 10 11 | 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 a more specific question would be: what should I pay attention to on that code in order to become evident that
ag.run(x) would be a call to an unbound function?
如果你还是想让
如果你仍然想让你的类的用户明白
您在代码中使用了未绑定方法(非线性极性步骤):
1 2 3 | activationFunctions = { 'bipolar': nonLinearBipolarStep , } |
更长的答案:方法是在类体中定义的函数,并且总是使用至少一个自变量,即self(除非使用@staticfunction并将其转换为普通函数)。"自我"是给定类的对象,在该类上调用方法(类似于C++中的方法)。在Python中,这个参数几乎没有什么特别之处,它不必被命名为self。现在,当您调用未绑定的方法时,您给出的第一个参数将被解释为self和consumed。如果调用绑定方法,则不会发生这种消耗(该方法已经有自己的对象)。例如:
1 2 3 4 5 | 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 |
更新:为什么它会起作用。简短回答:因为点(.)运算符将在可以更新未绑定方法时将其更新为绑定。
考虑一下,当你写a.foo(1)时会发生什么。首先,python检查对象a中的foo,但没有找到任何内容(foo不是分配给a的值)。所以它进入了一个类(名为a),你看,foo就在那里并且被使用了。但这里有个诀窍。python会将对象a绑定到未绑定的方法a.foo(细节现在不在这里了,所以想象一下龙会这样做),并将其绑定到绑定方法中。所以a.foo是有约束的,从参数中不再需要自我,所以1进入参数x,一切都正常。
现在来看看你的代码:在map中使用"双极":非线性极性步骤,这是未绑定的方法。然后在constructor(init)中,将self.run设置为从u getactivation返回的值,该值取自activationfunctions映射。在给定的示例中,您返回非线性极性步骤未绑定方法并将其分配给self.run。现在你给ag.run打电话。按照前面段落ag.run中的逻辑,首先查看ag对象内部。这是你的错误-找到了。因为python在ag对象中找到ag.run值,所以它从未为run对象咨询ag类型(activation),也从未有机会绑定它。所以ag.run是一个未绑定的方法,首先需要自变量。
一般来说,你有两个选择。要么执行ag.run(ag,4),这将起作用,但它很难看,要么手动将方法绑定到构造函数中的self。后者您可以这样做:
1 | self.run = self._getActivation(func).__get__(self) |
我认为这里让您困惑的是,您通过类属性
1 2 3 4 5 6 | class Class: def method(self, foo, bar): print(self, foo, bar) methods = {'method': method} |
当我们直接从字典调用方法时:
1 2 | >>> Class.methods['method'](1, 2, 3) 1 2 3 |
您可以看到我们正在将
1 2 3 | >>> instance = Class() >>> instance.method(1, 2) <__main__.Class object at 0x...> 1 2 |
现在我们的论点是
在这种情况下,由于您实际上不需要方法中的实例状态,因此只需将其设置为常规函数(注意对PEP-8遵从性的细微修改):
1 2 3 4 5 6 7 8 9 10 11 12 | 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, } ... |
这可能不那么令人困惑。