Python继承:何时以及为什么使用__init__?

6

我是一名Python新手,正在尝试理解继承方法背后的哲学/逻辑。问题最终涉及到为什么以及何时必须在子类中使用__init__方法。例如:

看起来从超类继承的子类不需要自己的构造函数(__init__)方法。下面是一个狗继承了哺乳动物的属性(名称、年龄)和方法(makenoise)。你甚至可以添加一个方法(do_a_trick)。所有的东西都像“应该”的那样工作。

然而,如果我想要在子类中添加一个新的属性,就像我在Cats类中尝试的那样,我会得到一个错误,说"self"未定义。但是我在狗类的定义中使用了"self"。这之间的区别是什么?

似乎要按照我的意愿来定义Cats,我需要使用__init__(self,name)super()__init__(name)。为什么会有这种差异呢?

class Mammals(object):

  def __init__(self,name):
    self.name = name
    print("I am a new-born "+ self.name)  
    self.age = 0

  def makenoise(self):
    print(self.name + " says Hello")

class Dogs(Mammals):

  def do_a_trick(self):
    print(self.name + " can roll over")

class Cats(Mammals):

 self.furry = "True"  #results in error `self' is not defined


mymammal = Mammals("zebra") #output "I am a new-born zebra"
mymammal.makenoise()  #output "zebra says hello"
print(mymmmal.age)    #output 0

mydog = Dogs("family pet") #output "I am a new-born family pet"
mydog.makenoise()  #output "family pet says hello"
print(mydog.age)  # output 0
mydog.do_a_trick() #output "family pet can roll over"

__init__ 不是构造函数,而是初始化器,它在已经构造好的 self 实例上工作。 - Azat Ibrakov
7个回答

4

显式优于隐式。

但是,你可以做以下操作:

class Dogs(Mammals):
    def __init__(self):
        #add new attribute
        self.someattribute = 'value'
        Mammals.__init__(self)

或者

class Dogs(Mammals):
    def __init__(self):
        #add new attribute
        self.someattribute = 'value'
        super(Mammals, self).__init__()

3
如果我想在子类中添加一个新属性,就像我试图在Cats类中做的那样,我会收到一个错误,说“self”未定义。但是我在dog类的定义中使用了“self”。
在你的超类Mammal中,你有一个__init__函数,它接受一个参数,你选择将其称为self。当你在__init__函数体内时,这个参数是在范围内的 - 它像任何局部变量一样是一个局部变量,并且在其包含的函数终止后不可能引用它。 Dog类上定义的函数do_a_trick也采用名为self的参数,并且它也是局部变量。 使这些变量特殊的不是它们的名称(你可以将它们称为任何你想要的),而是作为Python实例方法的第一个参数,它们获得对其调用的对象的引用作为值。(再读几遍最后一句话,这是理解这一点的关键,你可能第一次不会明白。)
现在,在Cat中,你有一行代码根本不在任何函数中。此时没有任何内容处于范围之内,包括self,这就是为什么会失败的原因。如果您在Cat中定义了一个名为self的函数参数,那么可以引用该参数。如果该参数恰好是Cat实例方法的第一个参数,则具有被调用的Cat实例的值。否则,它将具有传递给它的任何内容。

谢谢!我犯的错误是认为像def makenoise(self)或def init(self, name)这样的实例方法正在将“self”所指的内容作为输入进行处理,而实际上它们是在将“self”(或任何其他术语)设置为对象的名称。因此,使用__init__(foo, whatever)会将foo 设置为对象的名称,以便您随后可以定义其属性和方法。我理解得对吗? - user3486991

2
在Python类的顶层声明成为类属性。如果您来自C++或Java背景,这类似于声明静态成员变量。您不能在该级别分配实例属性。
变量self通常是指类的特定实例,即调用方法的实例。当使用语法inst.method()进行方法调用时,函数的第一个参数是调用该方法的对象inst。在您的情况下,并且通常按照惯例,在方法体内该参数被命名为self。然后,您可以将self视为仅在方法体中有效的标识符。您的赋值self.furry = True并未在方法中进行,因此在那里未定义self。
基本上,您有两个选择来实现所需的内容。第一种方法是将furry正确定义为cat类的属性:
class Cat(Mammals):
    furry = True

    # Rest of Cat implementation ...

或者你可以在猫的构造函数中设置实例变量furry的值:
class Cat(Mammals):
    def __init__(self):
        super(Mammals, self).__init__(self)
        self.furry = True

    # Rest of Cat implementation ...

如果你要学习Python,我强烈建议阅读Python文档的以下两个部分:
- Python类 - Python数据模型特殊方法(更高级)

2
如其他回答所指出的那样,在其他函数中看到的self实际上是一个参数。按照Python惯例,实例方法的第一个参数总是self
Cats从其基类Mammals继承了__init__函数。您可以重写__init__,并且可以调用或不调用基类实现。
如果Cats__init__想要调用基本实现,但不想关心参数,则可以使用Python可变参数。这在以下代码中显示。
类声明:
class Cats(Mammals):

  def __init__(self, *args):
    super().__init__(*args)
    self.furry = "True"

请看下面这个Stack Overflow问题,它与变量参数的星号表示法有关: Can a variable number of arguments be passed to a function? 额外的测试代码:
cat = Cats("cat")
print(vars(cat))

输出:

I am a new-born cat
{'name': 'cat', 'age': 0, 'furry': 'True'}

1
你可以像Chankey的回答中那样,在构造方法中初始化所有变量,例如__init__
不过,你也可以像这样做。
class Cats(Mammals):

 furry = "True"

然后

cat = Cats("Tom")
cat.furry # Returns "True"

你不能在函数外使用 self 的原因是因为 self 仅用于类的实例。如果在外部使用它,那会导致歧义。如果我的回答不清楚,请在评论区告诉我。


1
< p > __init__ 方法在创建类的实例时只运行一次。因此,如果您想在创建实例时设置属性,那就是您需要进行的操作。 self 是一个特殊的关键字,作为每个方法的第一个参数传递,并且它指的是实例本身。在这方面,__init__ 与其他方法没有区别。

“差异的本质是什么”:您定义了方法 Dog.do_a_trick,并像往常一样将 self 作为参数传递给该方法。但是在 Cat 中,您无意中(也许是下意识地!)尝试在类范围内工作 - 这是设置所有猫的相同值的 类属性 的方式:

class Cat(object):
    sound = "meow"

它是不同的,所以你可以同时拥有两个选项。有时(不总是,但偶尔)类属性是一个有用的东西。所有猫都发出相同的声音。但大多数情况下,你会使用实例属性——不同的猫有不同的名字;当你需要这个时,使用__init__


1
假设你有一个叫做 Person 的类,其中定义了一个名为 get_name 的方法:
 class Person():
     def __init__(self, first_name, last_name):
         self.first_name = first_name
         self.last_name = last_name

     def get_name(self):
         return self.first_name + ' ' + self.last_name

你创建了一个Person实例,名为p1。现在当你使用这个实例调用get_name()函数时,它会在内部进行转换。

    Person.get_name(p1)

所以,self 就是实例本身。
没有 self,你可以这样编写上面的代码:
 class Person():
     first_name = None
     last_name = None


     def get_name(personobject):
         return personobject.first_name + ' ' + personobject.last_name

我想说的是,名称self只是一种约定。
对于继承,如果您想在子类中拥有额外的属性,则需要先初始化超类,然后根据需要添加参数。 例如,如果您想从Person创建一个名为Boy的子类,并添加新属性height,则可以定义如下:
class Boy(Person):
     def __init__(self, first_name, last_name, height):
         super(Person, self).__init__(first_name, last_name)
         self.height = height

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接