为什么不用Python返回对象?

时间:2018-11-25 06:39:44

标签: python python-3.x

我一般是Python和编程的新手,而我刚接触OOP。在Python中,在函数名称空间中定义的任何内容仅在该名称空间中才有效。一旦超出空间,诸如变量之类的东西就会被遗忘:

def foo():
    a = 3

print(a)

NameError: name 'a' is not defined

据我所知,除了从函数返回数据外,函数内部的任何信息在函数调用结束时都会丢失,这是我的问题。输入以下代码:

class Example:

    def __init__(self, name):
        self.name = name

    def foo(self):
        self.name = 'John'

bar = Example('Jake')
bar.foo()
print(bar.name)

'John'

我的问题是:为什么不必须在方法之后返回对象?在正常函数中,任何变量都被遗忘,但是在方法中,似乎数据实际上已附加到对象本身上,例如foo()方法尽管引用{{ 1}}首先在另一种方法中被引用。它是否正确?还是有更好的技术解释?

5 个答案:

答案 0 :(得分:5)

首先,您不必在方法中,在python中以及在许多其他编程语言中都返回值。例如:

def append_a(lst):
    lst.append('a')

bob = []
append_a(bob)
print(bob)
['a']

上面我们没有在函数中返回任何内容,但是我们使用它来修改现有的数据结构,这几乎在任何地方都是很常见的。

第二,在第二个示例中,创建了Example类的实例,当您查看self.something时,您正在查看该类的成员,这与其他语言不同,在其他语言中,成员通常只声明一次,在python中,您可以动态添加成员。
因此,当查看bar.name时,您正在查看该类的成员,其值在实例bar上。如果您查看其他实例,则值将不同。

class Example:

    def __init__(self, name):
        self.name = name

    def foo(self):
        self.name = 'John'

bar = Example('Jake')
bob = Example('Bob')
bar.foo()
print(bar.name)
print(bob.name)

John
Bob

答案 1 :(得分:4)

要了解这一点,您需要了解自我的运作方式。您可以在此处了解更多信息: Understanding self in python

简而言之,self是指调用对象。调用self.variable是指与调用对象关联的变量。 Python足够聪明,可以创建一个不存在的Python。

在类内部调用self.variable与使用对象引用调用object.variable一样

请考虑以下示例来证明这一点:

class Example:
    def print_x(self):
        print(self.x)

obj = Example()
obj.x = 5;    # Create a new attribute of the object and assign it a value 5
print(obj.x)  # Outputs 5
obj.print_x() # Outputs 5

在您的示例中,我添加了一些打印语句,以帮助您了解执行期间程序的状态:

    class Example:
    def __init__(self, name):
        print(dir(self)) # Printing object contents before initializing name
        self.name = name # New attribute 'name' created
        print(dir(self)) # Printing object contents after initializing name

    def foo(self):
        print("Before foo, self.name = "+ self.name)
        self.name = 'John'
        print("After foo, self.name = "+ self.name)


bar = Example('Jake')
bar.foo()
print(bar.name)

以上代码的输出为

['__doc__', '__init__', '__module__', 'foo']
['__doc__', '__init__', '__module__', 'foo', 'name']
Before foo, self.name = Jake
After foo, self.name = John
John

我将引导您完成此代码。当我们第一次创建bar时,将调用__init__()方法。在这里,我们使用dir(self)打印对象的内容。输出['__doc__', '__init__', '__module__', 'foo']表示该对象只有一个成员,即'foo'方法。

现在,我们创建一个名为“ name”的新属性,并为其分配值“ Jake”。因此,该对象现在具有另一个成员,即“名称”属性,如下一个dir(self)['__doc__', '__init__', '__module__', 'foo', 'name']

的输出所示

现在,我们调用foo方法并在该方法之前和之后打印值。在foo中更改name之前,与对象关联的name的值为“ Jake”。但是,name更改后,self.name的值为“ John”。这由

表示
Before foo, self.name = Jake
After foo, self.name = John`

接下来我们验证通过更改self.name所做的更改是否确实已经通过打印name来更改barbar.name的值John,从而为我们提供了预期的输出{{ 1}}

现在回到您的问题,self.name不是某个方法内的普通变量,当我们超出范围时,该方法会丢失。 self基本上可以在类内部的任何地方用于引用调用对象(在这种情况下为bar)。它用于操纵此调用对象。现在,由于bar在范围内,我们可以打印其name属性。

  

在常规函数中,任何变量都被遗忘,但在方法中,它却被遗忘   似乎数据实际上是附加到对象本身上的

self处理对象中的属性,并且不限于方法的范围。

答案 2 :(得分:3)

方法和函数之间没有太多区别(有关详细信息,请参见此thread

尽管您可能会立即注意到一个重要的区别,就是方法将 self 作为其第一个参数,而我们说方法 foo 与实例“绑定”类 Example bar ,仅表示将使用其第一个参数( self )作为实例来调用 foo bar )本身

借助这些知识,我们可以了解以下功能的作用:

class Example:

  def __init__(self, name):
    self.name = name

  def foo(self):
    self.name = 'John'

bar = Example('Jake')

在方法 init 中,将 name 分配给 self 。但是自我是什么?它本身就是 bar ,因此可以将调用 init 视为

bar.name = 'Jake'

然后,当您调用方法 foo

bar.foo()

您同样做到了

bar.name = 'John'

因此,当以下内容的最终输出为“ John”时,就不足为奇了

print(bar.name)  # John

关于您的查询,该方法不必返回任何内容,事实并非如此。方法和函数可能会也可能不会返回任何内容(请参见此answer)。但是在这种情况下,正在发生的事情是对传递给方法的对象进行了操作(自我,它是bar,被赋予了 name ),并且由于该对象在方法调用完成后仍处于活动状态,我们可以观察到该方法所做的更改(即,我们可以看到bar.name更改为“ John”)

此功能也可用于以下功能:

def another_foo(self):
  self.name = 'Mark'
baz = Example('Jake')
another_foo(baz)
print(baz.name)  # Mark

您看到,该函数也未返回任何内容。通过操纵其参数,它就像方法 foo 一样工作。实际上,您可以将其添加到 Example 类中,并像使用方法一样使用它

Example.another_foo = another_foo
new_bar = Example('Jake')
print(new_bar.name)  # Jake
new_bar.another_foo()
print(new_bar.name)  # Mark

答案 3 :(得分:2)

欢迎使用堆栈溢出。您正在朝正确的方向思考。我将从一个更基本的层次开始,以便其他初学者可以了解发生了什么。

可以考虑将Example作为模板来创建某种特定的新对象-意味着这些对象都将具有相同的属性(也称为属性)和功能。例如,通过对现实生活中的物体进行类比,所有汽车都具有“速度”属性和“加速”功能。

属性将特定于对象。例如,一辆汽车将以0 mph的速度行驶,而另一辆将以70 mph的速度行驶。

任何时候的属性值都描述对象的状态。您可以将其视为对象的功能的方法可以更改对象的状态。

bar是您使用模板(即classExample创建的对象。如果必须描述该对象的状态,请告诉我们其属性的值。在这种情况下,对象只有一个属性:name

现在,这是重要的部分:name不仅是任何变量,它还是您从类Example中创建的任何对象的属性。将name视为始终附加到对象。这就是为什么您写self.name = 'John'而不是name = 'John'的原因。在后一种情况下,name不会成为对象bar的一部分,并且该类中的其他任何方法都不能访问name变量。

因此,总而言之,当您从类中创建对象时,请将该对象视为具有各种属性。对象的所有方法或功能都可以访问所有这些属性。属性的值将在任何时候描述对象在那时的状态。通过该对象的方法,可以更改其状态。

最后,这是个很棒的工具,可以直观地显示代码每一行所发生的情况:pythontutor.com

答案 4 :(得分:1)

好吧,这就是在OOP中所谓的“作用域”。尽管类和函数之间的差异可能看起来很细微,但却有很大的差异。

简单地说,函数依赖于全局和局部变量。全局变量是您在全局工作区中定义的对象。这就像拥有一个工作台并开始在定义的函数和类中使用一个通用对象。因此,例如,如果您修改函数foo()以包含一个参数,例如:

a = 3 # This is globally defined

def foo(a):
  a = 3 * a
  return a

print(foo(a))
9

a的全局值更改为2会得到6。我们“返回”一个对象,因为它是在函数范围内局部定义的。在上面的示例中,foo()执行本地操作3 * a并将其本地定义为a本身并返回它。因此,您还可以在执行a跳过全局变量foo()并在函数范围内本地定义它:

print(foo(6)) # Locally defined
18

print(a)
3 # Returns global variable
另一方面,

类需要声明 ,成员对此做了很好的解释(见上文)。