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

MAS*_*ASL 7 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())
Run Code Online (Sandbox Code Playgroud)

这给出了以下错误:“ sq()接受0个位置参数,但给了1个 ”。

据我了解,其原因是在执行时,该函数在我们可能定义/调用的任何其他参数/​​自变量之前传递了对调用对象(调用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))
Run Code Online (Sandbox Code Playgroud)

我得到错误

nonLinearBipolarStep() missing 1 required positional argument: 'x'
Run Code Online (Sandbox Code Playgroud)

但是,一种变通方法(解决方案??)是将step函数定义为不带参数self(!)的

def nonLinearBipolarStep(x,string=None):
Run Code Online (Sandbox Code Playgroud)

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

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

线程“为什么在此方法中不使用self” 4并未向我提供明确的答案:上面代码的语法详细信息告诉我self不需要什么?例如,该代码与本教程示例有何不同?

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

    def f(self):
        return 'hello world'
Run Code Online (Sandbox Code Playgroud)

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

这使我怀疑我的代码是否不以某种方式隐藏定时炸弹:self作为值传递x?它可以按预期工作,所以我会拒绝,但随后我将面临这个难题。

我想我缺少该语言的一些关键思想。我承认,我也为参考文献3的OP提出的问题感到困惑。

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

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

Chr*_*ean 6

What is self?

In Python, every normal method is forced to accept a parameter commonly named self. This is an instance of class - an object. This is how Python methods interact with a classes state.

You are allowed to rename this parameter whatever you please. but it will always have the same value:

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


>>> cls = Class()
>>> cls.method()
<__main__.F object at 0x03E41D90>
>>> 
Run Code Online (Sandbox Code Playgroud)

But then why does my example work?

However, what you are probably confused about 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
>>> 
Run Code Online (Sandbox Code Playgroud)

This is because of the distinction between bound, and unbound methods in Python.

When we do this in __init__():

self.run = self.methods['method']
Run Code Online (Sandbox Code Playgroud)

We are referring to the unbound method method. That means that our reference to method is not bound to any specific instance of Class, and thus, Python will not force method to accept an object instance. because it does not have one to give.

The above code would be the same as doing this:

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


>>> Class.method(3)
3
>>> 
Run Code Online (Sandbox Code Playgroud)

In both examples, we are calling the method method of the class object Class , and not an instance of the Class object.

We can further see this distinction by examining the 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>>
>>> 
Run Code Online (Sandbox Code Playgroud)

As you can see, in the first example when we do Class.method, Python shows: <function Class.method at 0x03E43D68>. I've lied to you a little bit. When we have an unbound method of a class, Python treats them as plain functions. So method is simply a function that is not bound to any instance of `Class.

However in the second example, when we create an instance of Class, and then access the method object of it, we see printed: <bound method Class.method of <__main__.Class object at 0x03BD2FB0>>.

The key part to notice is bound method Class.method. That means method is bound to cls - a specfic an instance of Class.

General remarks

As @jonshapre mentioned, writing code like in your example leads to confusion (as proof by this question), and bugs. It would be a better idea if you simply defined nonLinearBipolarStep() outside of Activation, and reference that from inside of 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,
    }

    ...
Run Code Online (Sandbox Code Playgroud)

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?

If you'd still like to let nonLinearBipolarStep be unbound, then I recommend simply being carefully. If you think your method would make for the cleanest code then go for it, but make sure you know what you are doing and the behavior your code will have.

If you still wanted to make is clear to users of your class that ag.run() would be static, you could document it in a docstring somewhere, but that is something the user really shouldn't even have to be concerned with at all.


Blc*_*ght 6

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

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

函数是描述符。这意味着当您将函数保存为类变量时,__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
Run Code Online (Sandbox Code Playgroud)