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

新手上路,请多包涵

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

来自其他语言(JavaScript、C++、Haskell、Fortran 等),我很难 理解 Python 上的一些东西。一件事是类方法中 自我 的以下区别。

例如,下面的代码显然是行不通的:

 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() takes 0 positional arguments but 1 was given ”的错误。

据我了解,原因是在 _执行时_,函数被传递给调用对象(调用 sq 的实例)的引用作为第一个参数,然后我们可能已经定义/调用了 sq 的任何其他参数/参数。所以解决方法很简单:把sq的代码 def sq(self): 。实际上, Python 教程 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 (!)的 step 函数

def nonLinearBipolarStep(x,string=None):

然后我得到预期的行为(至少对于这个简单的测试) 1 。因此,这里不仅不需要 self而且在这里的使用也是不正确的!

但是根据上面提到的教程,或者像 this 2this 3 这样的线程中的答案,在我看来这段代码不应该工作……或者应该在某些时候产生一些意想不到的后果(?)。 事实上,如果我在 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'

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

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

我想我错过了该语言的一些关键思想。我承认我也在为参考文献 3 的 OP 提出的问题而苦恼^。

[^]: 在 JS 中,只需在函数体中使用 this ,并且函数本身被定义为对象原型的成员或实例成员,然后使用…正确分配它 this

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

原文由 MASL 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 691
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
>>>

在这两个示例中,我们都调用了类对象 Class 的方法 method --- ,而 不是 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>>
>>>

如您所见,在第一个示例中,当我们执行 Class.method 时,Python 显示: <function Class.method at 0x03E43D68> 。我对你撒了一点谎。当我们有一个类的未绑定方法时,Python 将它们视为普通函数。所以 method 只是一个未绑定到 `Class 的任何实例的函数。

然而在第二个例子中,当我们创建 Class 的实例,然后访问它的 method 对象时,我们看到打印: <bound method Class.method of <__main__.Class object at 0x03BD2FB0>>

要注意的关键部分是 bound method Class.method 。这意味着 method **绑定\*\* 到 cls - Class 的一个特定实例。

一般说明

正如@jonshapre 所提到的,像您的示例中那样编写代码会导致混淆(作为这个问题的证明)和错误。如果您只是在 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 ,那么我建议您要小心。如果你认为你的方法会产生最干净的代码,那就去做吧,但要确保你知道你在做什么以及你的代码将具有的行为。

如果你仍然想让你的班级的用户清楚 ag.run() 是静态的,你 可以 在某个地方的文档字符串中记录它,但这是用户真的不应该关心的事情根本。

原文由 Christian Dean 发布,翻译遵循 CC BY-SA 4.0 许可协议

您遇到了 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

原文由 Blckknght 发布,翻译遵循 CC BY-SA 3.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题