Python类方法:什么时候不需要自己

时间:2017-06-24 21:16:45

标签: python

我试图使用类重写一些代码。在某些时候,我想要的是使用对象的每个实例的参数值为成员函数分配特定的定义。

来自其他语言(JavaScript,C ++,Haskell,Fortran,...)我正在努力理解 Python上的一些东西。有一点是类方法中 self 的以下区别。

例如,以下代码显然不会起作用:

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 with。所以解决方案很简单:将sq的代码更改为def sq(self):。实际上,Python tutorial 1 似乎建议始终使用self作为第一个参数定义对象方法。这样做我们得到了预期666 666 49。到目前为止一切都很好。

然而,当我尝试像这样实现我的课程时:

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))

我收到错误

nonLinearBipolarStep() missing 1 required positional argument: 'x'

然而,一种解决方法(解决方案?)是在没有参数self(!)的情况下定义步骤函数

def nonLinearBipolarStep(x,string=None):

然后我得到1的预期行为(至少对于这个微不足道的测试)。所以,这里不仅需要self ,而且在这里使用它甚至是不正确的!

但是根据上面提到的教程,或者this 2this 3这样的帖子中的答案,在我看来这个代码不应该& #39;工作......或者在某些时候应该有一些意想不到的后果(?)。的确,如果我删除了self定义中对_getActivation的所有引用,我得到了我根据该规则可以理解的错误消息_getActivation() takes from 0 to 1 positional arguments but 2 were given

帖子"Why is self not used in this method" 4没有给我一个明确的答案:上面代码的语法细节告诉我不需要self例如,该代码与本教程示例的不同之处

class MyClass:
    """A simple example class"""
    i = 12345

    def f(self):
        return 'hello world'

?实例化这个类按预期工作,但如果定义为none,它会抱怨缺少参数(我知道它可能是任何标签)。

这让我怀疑我的代码是否没有以某种方式隐藏定时炸弹:self是否作为x的值传递?它按预期工作,所以我说不,但后来我面对这个难题。

我想我错过了一些关键语言的想法。我承认我也很难解决参考文件3提出的问题^。

[^]:在JS中,只需在函数体中使用this,函数本身既可以定义为对象原型的成员,也可以定义为实例成员,然后使用它正确分配... this

修改 线程很长。对于那些浏览一些帮助的人来说,如果您不熟悉Python,那么您可能需要检查所选的解决方案及其注释。但是,如果您已经了解Python中的绑定/未绑定方法,则只需要直接检查描述符的使用,如Blckknght的回答中所述。我终于在我的代码中选择了这种方式,使用__get__来分配运行。

4 个答案:

答案 0 :(得分:2)

什么是self

在Python中,每个普通方法都被迫接受一个通常名为self的参数。这是一个类的实例 - 一个对象。这就是Python方法与类状态交互的方式。

您可以随意重命名此参数。但它总是具有相同的值:

>>> class Class:
    def method(foo): # 
        print(foo)


>>> cls = Class()
>>> cls.method()
<__main__.F object at 0x03E41D90>
>>> 

但为什么我的例子有效?

但是,您可能感到困惑的是这段代码的工作方式不同:

>>> 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中绑定未绑定方法之间的区别。

当我们在__init__()中执行此操作时:

self.run = self.methods['method']

我们指的是 未绑定 方法method。这意味着我们对method的引用未绑定到Class的任何特定实例,因此,Python不会强制method接受对象实例。因为它没有给人。

上述代码与执行此操作相同:

>>> class Class:
    def method(foo):
        print(foo)


>>> Class.method(3)
3
>>> 

在这两个示例中,我们调用类对象method的方法Class,而 {{1> 实例对象。

我们可以通过检查绑定和未绑定方法的Class来进一步看到这种区别:

repr

正如您所看到的,在我们执行>>> 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>> >>> 的第一个示例中,Python显示: Class.method。我对你撒谎了一下。当我们有一个类的非绑定方法时,Python将它们视为普通函数。所以<function Class.method at 0x03E43D68>只是一个没有绑定到任何`Class。

实例的函数

但是在第二个示例中,当我们创建method的实例,然后访问它的Class对象时,我们会看到打印:method

要注意的关键部分是<bound method Class.method of <__main__.Class object at 0x03BD2FB0>>。这意味着bound method Class.method 绑定 method - 一个特定的cls实例。

一般性评论

正如@jonshapre所提到的,在你的例子中编写代码会导致混淆(作为这个问题的证明)和错误。如果您只是在Class的之外定义nonLinearBipolarStep() ,并从Activation内部引用该内容,那将是一个更好的主意:

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, } ... 是否会调用未绑定的函数,我应该注意哪些代码?

如果您仍然希望ag.run(x)不受约束,那么我建议您谨慎行事。如果您认为您的方法可以获得最干净的代码,那就去做吧,但要确保您知道自己在做什么以及代码会有什么行为。

如果你仍想让你的班级用户明白nonLinearBipolarStep是静态的,你可以在某个地方的文档字符串中记录它,但这是用户真正不应该做的事情。甚至不得不关心。

答案 1 :(得分:2)

你正在遇到Python方法实现的一个更微妙的部分。它归结为正常方法调用的self参数(例如some_instance.method())是如何绑定的。它使用“描述符”协议,该协议没有很好的文档记录(至少,它对新的Python程序员来说并不明显)。

描述符是一个具有__get__方法(以及可选的__set__和/或__delete__方法的对象,但我只是在这里讨论__get__ )。当这样的对象存储在类变量中时,只要在实例上查找相应的名称,Python就会调用其__get__方法。请注意,存储在实例变量中的描述符对象不会发生这种特殊行为,只会发生类变量。

函数是描述符。这意味着当您将函数保存为类变量时,在实例上查找时将调用其__get__方法。该方法将返回一个“绑定方法”对象,该对象将自动将self参数传递给函数。

如果将函数存储在除顶级类变量之外的某个位置(例如在字典或实例变量中),则不会获得此绑定行为,因为在此时不会调用描述符协议查找对象。这通常意味着您需要手动传递self,或者您应该首先从函数定义中省略self参数(在这种情况下,我建议将函数移出类)明确表示不打算用作方法。)

但是如果你愿意,你也可以手工构建绑定方法。该类型在types模块中公开,为types.MethodType。所以你可以改变这样的代码,它应该可以工作:

def __init__(self,func=None):
    if func == None: func=self.default 
    self.run = types.MethodType(self._getActivation(func), self) # be sure to import types

答案 2 :(得分:1)

我认为你在这里困惑的是你通过类属性activationFunctions访问方法,而不是(实际上通常会被访问的实例)访问实例本身。例如,给定:

class Class:

    def method(self, foo, bar):
        print(self, foo, bar)

    methods = {'method': method}

当我们直接从字典中调用方法时:

>>> Class.methods['method'](1, 2, 3)
1 2 3

您可以看到我们将1作为self参数传递;该方法未在实例上调用,因此没有注入实例。相比之下,当我们在一个实例上调用它时:

>>> instance = Class()
>>> instance.method(1, 2)
<__main__.Class object at 0x...> 1 2

现在我们的参数是foobar,实例是self。这就是为什么你认为似乎需要不同数量的参数。

在这种情况下,由于您实际上并不需要方法中的实例状态,只需将其设为常规函数(请注意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,
    }

    ...

这可能不那么令人困惑。

答案 3 :(得分:1)

您在此代码中使用了未绑定的方法(nonLinearBipolarStep):

activationFunctions = {
    'bipolar': nonLinearBipolarStep ,
}

更长的答案:方法是在类体中定义的函数,并且始终至少使用一个参数,因此称为self(除非使用@staticfunction并将它们转换为普通函数)。 Self是给定类的一个对象,调用哪个方法(在C ++中就像这样)。在python中,这个论点几乎没什么特别之处,它不必被命名为self。现在,当您调用未绑定的方法时,您给出的第一个参数将被解释为self和consume。如果你调用绑定方法,那么这种消耗不会发生(该方法已经有了自己的对象)。例如:

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 for foo并且什么都没找到(foo不是赋给a的值)。所以它进入一个类(名为A)并且看到 - foo在那里并被使用。但这是一个技巧。 Python会将对象a绑定到未绑定的方法A.foo(细节现在让我逃避,所以想象龙做了它)并使其成为绑定方法。所以a.foo是绑定的,并且不再需要自己的参数,因此1进入参数x并且一切正常。

现在你的代码:你在map中使用'bipolar':nonLinearBipolarStep,这是一种未绑定的方法。然后在构造函数( init )中将self.run设置为从_getActivation返回的值,该值取自activationFunctions map。在给定的示例中,您返回nonLinearBipolarStep未绑定方法并将其分配给self.run。现在你打电话给ag.run。按照前一段中的逻辑ag.run首先查看ag对象内部。这是你的错误 - 它找到了。当python在ag对象中找到ag.run值时,它从不为运行对象查询ag类型(Activation),也从未有机会绑定它。所以ag.run是一种未绑定的方法,并期望自我论证为第一。

您通常有两种选择。要么ag.run(ag,4),它将工作,但它的丑陋,或手动绑定方法自我在构造函数中。后者你可以这样做:

self.run = self._getActivation(func).__get__(self)