【问题标题】:Class instantiation and 'self' in pythonpython中的类实例化和“自我”
【发布时间】:2012-08-06 23:42:52
【问题描述】:

我知道关于这个主题已经写了很多。但是,我无法吸收其中的大部分内容。也许是因为我是一个完全的新手,在没有任何计算机科学培训的情况下自学。无论如何,如果你们中的一些有头脑的人在这个具体的例子中插话,你会帮助像我这样的其他初学者。

所以,我编写了以下函数,当我调用它(作为一个模块?)时它工作得很好,因为它是一个名为“funky.py”的文件:

我在终端中输入以下内容:

python classy.py

它运行良好。

def load_deck():
    suite = ('Spades', 'Hearts')
    rank = ('2', '3')
    full_deck = {}
    i = 0
    for s in suite:
        for r in rank:
            full_deck[i] = "%s of %s" % (r, s)
            i += 1
    return full_deck

print load_deck()

但是,当我将相同的函数放在一个类中时,我得到了一个错误。

这是我的“classy.py”代码:

class GAME():
    def load_deck():
        suite = ('Spades', 'Hearts')
        rank = ('2', '3')
        full_deck = {}
        i = 0
        for s in suite:
            for r in rank:
                full_deck[i] = "%s of %s" % (r, s)
                i += 1
        return full_deck
MyGame = GAME()
print MyGame.load_deck()

我收到以下错误:

Traceback (most recent call last):
File "classy.py", line 15, in <module>
print MyGame.load_deck()
TypeError: load_deck() takes no arguments (1 given)

因此,我将定义行更改为以下内容,它工作正常:

def load_deck(self):

把一个函数放在一个需要使用'self'的类中是什么意思。我知道“自我”只是一个约定。那么,为什么需要任何论证呢?从类中调用函数时,它们的行为会有所不同吗?

另外,这几乎更重要,为什么我的课程没有使用 init 的好处?使用 init 会对我的班级做什么?

基本上,如果有人有时间像我 6 岁一样向我解释这一点,那会有所帮助。提前感谢您的帮助。

【问题讨论】:

  • 这就是 Python 传递接收者的方式:[n instance] 方法的第一个参数。一个方法仍然需要一个参数(尽管它可以被命名为任何东西),因为 python 对传递 [非可选] 参数的确切数量是严格的。 (其他语言,如 Ruby 和 JavaScript 有一个特殊的关键字/绑定,但没有 Python ..)

标签: python class function instantiation self


【解决方案1】:

在类定义中定义一个函数会调用一些将其转换为方法描述符的魔法。当您访问 foo.method 时,它将自动创建一个绑定方法并将对象实例作为第一个参数传递。您可以使用 @staticmethod 装饰器来避免这种情况。

__init__ 只是在创建类以执行可选设置时调用的方法。 __new__ 是实际创建对象的对象。

这里有一些例子

>>> class Foo(object):
    def bar(*args, **kwargs):
        print args, kwargs

>>> foo = Foo()
>>> foo.bar
<bound method Foo.bar of <__main__.Foo object at 0x01C9FEB0>>
>>> Foo.bar
<unbound method Foo.bar>
>>> foo.bar()
(<__main__.Foo object at 0x01C9FEB0>,) {}
>>> Foo.bar()

Traceback (most recent call last):
  File "<pyshell#29>", line 1, in <module>
    Foo.bar()
TypeError: unbound method bar() must be called with Foo instance as first argument (got nothing instead)
>>> Foo.bar(foo)
(<__main__.Foo object at 0x01C9FEB0>,) {}

【讨论】:

  • 重点是你有很多类的实例——你需要self 知道你正在处理哪个实例。如果你不需要很多实例,你应该问问自己是否需要一个类。
  • 好的。这是一个开始。什么是绑定方法?将对象实例作为第一个参数传递是什么意思?这是否意味着函数将函数本身作为第一个参数传递?传给什么?而且,为了什么?写“自我”能解决什么问题?
  • 绑定方法意味着对象实例已经作为第一个参数传递。你可以把它想象成一个 lambda。当您访问类实例上的属性时,您会得到一个未绑定的方法。然后需要自己传递object参数。
  • @dwstein 这是一个和其他变量一样的变量。正如x 可以引用任何数字一样,self 可以引用您的类的任何实例(或者更确切地说,是在该点上运行该方法的实例)。运行该方法时,第一个参数 (self) 会自动填充此实例。
  • 附带说明,您确实需要了解类和实例之间的区别。指向类的指针没有任何意义,因为类只是一种模式或配方(对象应该是什么样子)。 self 是一个指向当前类实例的指针(它是真正的对象,根据类定义创建)。
【解决方案2】:

那么,为什么需要任何参数呢?

访问当前类实例的属性。

假设您有一个具有两种方法的类,load_deckshuffle。在load_deck 结束时,你想洗牌(通过调用shuffle 方法)

在 Python 中你会做这样的事情:

class Game(object):
    def shuffle(self, deck):
        return random.shuffle(deck)

    def load_deck(self):
        # ...

        return self.shuffle(full_deck)

将此与大致等效的 C++ 代码进行比较:

class Game {
    shuffle(deck) {
        return random.shuffle(deck);
    }
    load_deck() {
        // ...

        return shuffle(full_deck)
    }
}

shuffle(full_deck) 行上,首先它查找一个名为shuffle 的局部变量——它不存在,接下来它检查更高的级别,并找到一个名为shuffle 的实例方法(如果它不存在) t 存在,它将检查具有正确名称的全局变量)

这没关系,但不清楚shuffle 是指某个局部变量还是实例方法。为了解决这种歧义,也可以通过this 访问实例方法或实例属性:

...
load_deck() {
    // ...

    return this->shuffle(full_deck)
}

this 几乎与 Python 的 self 相同,只是它不作为参数传递。

为什么将self 作为有用的参数很有用? The FAQ lists several good reasons - 这些可以用"The Zen of Python" 中的一行来概括:

Explicit is better than implicit.

这是由The History of Python 博客中的帖子支持的,

我决定放弃隐式引用实例变量的想法。像 C++ 这样的语言允许您编写 this->foo 来显式引用实例变量 foo(以防有单独的局部变量 foo)。因此,我决定让这种显式引用成为引用实例变量的唯一方法。此外,我决定与其将当前对象(“t​​his”)设为特殊关键字,不如将“this”(或其等价物)设为方法的第一个命名参数。实例变量总是作为该参数的属性被引用。

使用显式引用,方法定义不需要特殊的语法,也不必担心与变量查找有关的复杂语义。相反,我们只需定义一个函数,它的第一个参数对应于实例,按照惯例,它被命名为“self”。

【讨论】:

    【解决方案3】:

    如果您不打算使用 self,您可能应该将该方法声明为 staticmethod

    class Game:
        @staticmethod
        def load_deck():
            ....
    

    这会撤消自动默认打包,该打包通常会将范围内的函数转换为将实例作为参数的方法。

    传递你不使用的参数会让其他试图阅读你的代码的人感到不安。

    大多数班级都有成员。你的没有,所以它的所有方法都应该是静态的。随着项目的发展,您可能会发现其中的所有函数都应该可以访问的数据,然后将这些数据放入 self 中,然后将其传递给所有函数。

    在这种情况下,应用程序本身是您的主要对象,__init__ 只是初始化所有这些共享值的函数。

    这是迈向面向对象风格的第一步,其中较小的数据片段被用作对象本身。但这是从直接脚本到 OO 编程的正常阶段。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-13
      • 2023-04-08
      • 2018-06-12
      • 1970-01-01
      • 2021-05-14
      • 1970-01-01
      • 1970-01-01
      • 2013-01-28
      相关资源
      最近更新 更多