Python类方法:何时不需要self

2024-05-15 04:12:19 发布

您现在位置:Python中文网/ 问答频道 /正文

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

来自其他语言(JavaScript、C++、Haskell、fortran……),我努力在Python上理解EME>理解EME>一些事情。一件事是在类内方法中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的任何其他参数/参数之前。所以解决方法很简单:将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(!)的step函数作为

def nonLinearBipolarStep(x,string=None):

然后我得到1的预期行为(至少对于这个琐碎的测试)。所以,这里不仅不需要self而且这里的用法也不正确!

但是根据上面提到的教程,或者像this 2this 3这样的线程中的答案,在我看来这段代码不应该工作…或者在某个点上应该会有一些意想不到的结果(?)。事实上,如果我删除了_getActivation定义中对self的所有引用,就会得到根据该规则可以理解的错误消息_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__


Tags: 对象实例方法函数代码self参数return
3条回答

什么是self

在Python中,每个normal方法都被迫接受一个通常名为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的引用没有绑定到Class的任何特定的实例,因此,Python不会强制method接受对象实例。因为它没有一个可以给予的。

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

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


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

在这两个例子中,我们调用类对象的方法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的实例,然后访问它的method对象时,我们看到printed:<bound method Class.method of <__main__.Class object at 0x03BD2FB0>>

需要注意的关键部分是bound method Class.method。这意味着methodcls绑定,这是Class的一个特定实例。

一般性意见

正如@jonshapre所提到的,在您的示例中编写类似的代码会导致混淆(作为这个问题的证明)和错误。如果您只在Activation外部定义nonLinearBipolarStep(),并从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 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?

如果您仍然想让nonLinearBipolarStep解除绑定,那么我建议您只需小心。如果您认为您的方法可以生成最干净的代码,那么就使用它,但是要确保您知道自己在做什么以及代码将具有的行为。

如果您还想让您的类的用户清楚地知道ag.run()是静态的,那么您可以将它记录在文档字符串中的某个地方,但这是用户根本不应该关心的事情。

我认为这里让您困惑的是,您是通过类属性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,
    }

    ...

这可能不那么令人困惑。

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

相关问题 更多 >

    热门问题