仍然不了解Python中的自我

时间:2015-05-07 13:17:47

标签: python self

所以我是Python的新手,面对这个自我提问。我已经研究了很多信息,但仍然没有掌握这个概念。 我从Zelle的书中得到一个简单的问题 - 为多边骰子写一个MSDie类(典型的骰子有6个方面)。每个MSDie对象都知道:1)它获得了多少边,2)它的当前值。所以这本书提出了以下代码:

class MSDie:

    def __init__(self,sides):
        self.sides=sides
        self.value=1

    def roll(self):
        self.value=randrange(1,self.sides+1,1)

    def getValue(self):
        return self.value

我想知道为什么只有没有自己才能使代码相同?

class MSDie:

    def __init__(sides):
        sides=sides
        value=1

    def roll():
        value=randrange(1,sides+1,1)

    def getValue(self):
        return value

有些人说需要自我来引用我们创建的实例对象。但我应该说我们使用的对象名称是指我们创建的对象。 为什么我不能用我的代码创建d1=MSDie(12)对象? Python将知道d1是一个MSDIe类对象,而calss具有那些声明的实例变量和方法。因此,当我想要setValue d1时,我只调用我的方法(没有self)并且它有效。请解释我的例子为什么我错了。

5 个答案:

答案 0 :(得分:4)

你是一个人;有很多Person类的实例,但只有一个

如果你想知道你的眼睛颜色,你可以在镜子里看看你自己。如果您想知道自己有多高,可以自己测量 。你不衡量人,因为人不是

同样,您的实例d1需要了解本身。 MSDie没有特定的value; d1必须查看自己的属性,并通过self引用这些属性。

如果其他人想要衡量您,并且您提供个人信息,则他们不必参考self。他们可以这样做:

Anton.height

Anton.weight

这对于非常简单的属性来说没问题,但是当任务更复杂时会变得更加困难。例如,考虑睡觉的行为。你必须:

  • 放下
  • 闭上眼睛
  • 减慢你的呼吸
  • 激活REM
  • 激活delta wave

这些任务有一个很好的包装器,称为sleep()。当你告诉自己sleep()时,你的身体会从那里接管:

def sleep(self):
    self.position = 'horizontal'
    self.eyelid = 'closed'
    self.breathing_rate = 30
    self.REM = True
    self.brainwaves.activate('Delta')

如果你必须自己设置所有这些属性和功能,那么你的童年就会非常艰难;在使用它们之前,您必须了解所有这些属性的含义。相反,Person类具有内置的功能,您只需在特定实例上调用sleep()

如果Person类在类本身而不是self上设置这些属性,该怎么办?如果可能的话,人的每一个例子都会同时躺下,闭上眼睛,缓慢呼吸等。通过引用self,您可以利用内置功能,而无需将公共API的功能部分细节(即必须在外部学习的东西)。

答案 1 :(得分:1)

Python实现实例方法的方式,每个方法都接收对调用该方法作为其第一个参数的对象的引用。当你写像

这样的东西
d1 = MSDie(12)
d1.roll()

Python首先检查d1的类型是什么,以便它知道要调用的方法。看到type(d1)MSDie,它会检查MSDie是否有一个名为roll的方法,并发现它确实存在。它调用该方法,将d1作为第一个参数传递,即d1.roll()等同于

MSDie.roll(d1)

roll()内,通过d1参数访问self的引用。 (参数的名称并不重要,但self是几乎普遍使用的惯例。)

如果您拨打d2.roll(),则会将其转换为MSDie.roll(d2),并且该来电会将self视为对d2的引用。

如果您将roll定义为

def roll():
    value=randrange(1,sides+1,1)

Python必须将d1.roll()转换为MSDie.roll(),并且该方法无法知道要使用哪个骰子对象。 value只是函数的局部变量,与任何特定对象无关。

答案 2 :(得分:0)

在某种程度上,这就是Python要求你写东西的方式。记住:类是一个蓝图,从中构建对象(即该类的实例)。

例如汽车(类)有轮子。因此我的汽车(一辆真正的汽车,即一个物体)有轮子。当我的汽车建成时,车轮已经安装在我的车上。这些说明可能会说"带轮并把它们放到目前正在建造的汽车上#34;。从概念上讲,重要的是要注意仅从车库中取出车轮并将其倾倒在汽车附近的某处是不够的。或者到蓝图上。但是,它可能会说"抓住一些螺丝并将它们放在桌子上供以后使用"。然后这些不会与最终的车一起交付(除非在后期建造)。

在Python中,事情是一样的。 __init__描述了构建实际对象时要执行的操作。它需要区分"使sides成为我骰子的属性"并且"在sides中存储一个数字,以便我以后可以在构造中使用它,但之后会忘记它#34;。

与声明类接口的语言(例如C ++,Java)相比,Python无法知道骰子是否有sides

在所有其他情况下,如果python实际上会检查MSDie是否具有属性名称sides并使用它,那么可能会认为这是一个明智的想法。但是,当您希望创建局部变量但实际更改对象时,它可能会导致更复杂的类中出现令人惊讶的结果。为了防止此类错误,Python要求您在处理属性时要明确。

答案 3 :(得分:0)

简短的回答是因为它就是这样设计的。接受并继续前进。

但是,设计有一些用处:命名空间和可读性。

请考虑以下事项:

foo = 'some value'

class Bar():
    def __init__(self):
        self.foo = foo

请注意,有两个名为foo的对象。一个在模块中全局定义(如foo),另一个在类的属性中定义(如self.foo)。如果我们没有self,那么这是不可能的。 Self为我们提供了一个不同的命名空间,以便我们可以区分分配给给定类实例的对象和另一个不关心名称冲突的对象。

使用同一个类的多个实例时也是如此。例如,当您需要比较两个实例(使用__eq__和朋友)时:

class Foo():
    def __eq__(self, other)
        return self.bar == other.bar

如果没有自我,那么bar哪个是不明确的。虽然在平等的#34;中并不是非常重要。比较,在"大于"或者"小于"比较,顺序很重要。通过使用self,它可以在你编写代码时清楚地表明,但是当你几个月/几年后回到它时也会清楚,并且必须阅读并理解它正在做什么。

考虑一个具有许多属性的非常大的类,它是具有许多对象的大模块的一部分。没有self,就不会总是清楚哪些对象是类属性,哪些对象不是没有读取整个模块。因此,即使自我的使用是可选的(如你所建议的那样),当你(或更重要的是其他人)稍后再回到它时,总是使用它来确保你的代码是清晰的仍然是一个好习惯。这只是一个很好的编程实践。我很高兴语言强迫它。事实上,Python的优势之一就是它的可读性。自我增加了可读性。拥抱它。

答案 4 :(得分:0)

了解类和对象(类实例)之间的区别非常重要。

类由方法声明和对象模板组成(例如,类有2个方法和1个整数属性)。它仅由类名(//doFlatten acts on a BinaryTreeNode and takes a storage list as an argument //it will call itself on any children on the node //if the node is a leaf, it will update the storage list and return that updated list public List<Integer> doFlatten(List<Integer> result){ if (this.leftChild != null){ this.leftChild.doFlatten(result); } else if (this.rightChild != null){ this.rightChild.doFlatten(result); } else{ result.add(this.value); //add leaf to result list return result; } } )标识。

Object(类实例)是对它的类的具体评估,即它具有类属性的具体值。它由类名称和它的属性(MSDie

的评估来识别

执行Python代码时,程序代码(方法和函数,以及#34;类&#34;)被加载到代码内存中。在程序执行期间,对象由执行的代码创建并存储在内存的另一部分 - data 内存中。每个属性的存储值与类实例的类的标识一起存在,但是在那里没有复制类代码(方法)。可以通过 data 存储器盒的地址(指针)来识别实例,其中存储了它的数据。值得一提的是,数据代码内存部分是严格分开的。请注意,可能有相同类的数千个实例( data 内存中有数千个实例框),但该类的代码只出现一次 - 在代码内存中。

在Python中调用instance方法时:

MSDie(sides=3, value=1)

实例名称(d1)被转换为指向d1&lt;#em>数据内存盒的指针。但该方框不包含方法d1.setValue(3) 的代码。只有类的标识,可用于在代码内存中查找该方法的代码。但是当从代码内存调用方法代码时,它不知道它应该使用什么实例。这些信息需要以某种方式通过。

大多数带有类的语言在代码编译成字节代码时处理实例到类方法的传递,并且在语言语法中根本不可见。它可能会让人感到困惑&#34;其中MSDie.setValue(x)(Java类似于Python this)的来源,因为它没有在任何地方定义。

Python在类方法中保留了这个实例参数作为其语法的一部分。所以它真的不是什么大问题,它在其他面向对象语言中的工作方式相同,它们只是隐藏在编译器之后。另一方面,可能有点令人困惑,类方法是用2个参数(self)定义的,但是当它被调用时,只传递1个参数(MSDie.setValue(self, x))。

所以回答你的问题:

当类方法定义如下:

d1.setValue(3)

您需要以这种方式调用它(不传递def MSDie: ... def roll(self): self.value=randrange(1,self.sides+1,1) 的值)

self

Python将在d1变量中找到保存的类实例,然后调用它的方法

d1.roll()

但是这种转换是在后台完成的。这就是方法MSDie.roll(d1) 内的{1}} d1实例的可用性。

你不能在类方法的定义中省略self参数,因为Python无法找到具有正确参数数量的方法,或者方法内部参数的值将是错误的(第一个参数在方法将从调用获得第二个参数等)。此外,方法内部的代码将无法获得正确的实例。

此外,您需要在类方法中使用roll()来正确访问所有对象属性(需要找到存储这些值的内存框)。