为什么在Python类中使用__init__?

150 投票
9 回答
200561 浏览
提问于 2025-04-17 08:52

我在理解类的初始化时遇到了一些困难。

类的意义是什么,我们应该在里面包含什么呢?写类和写函数是不是需要不同的思维方式?我原本以为可以直接写函数,然后把它们放进一个类里,这样就可以重复使用。这样做可以吗?

这里有一个例子:

class crawler:
  # Initialize the crawler with the name of database
  def __init__(self,dbname):
    self.con=sqlite.connect(dbname)

  def __del__(self):
    self.con.close()

  def dbcommit(self):
    self.con.commit()

或者另一个代码示例:

class bicluster:
  def __init__(self,vec,left=None,right=None,distance=0.0,id=None):
    self.left=left
    self.right=right
    self.vec=vec
    self.id=id
    self.distance=distance

我在阅读别人的代码时,看到很多类都有 __init__,但我不太明白创建它们的逻辑是什么。

9 个回答

6

这只是为了初始化实例的变量。

比如说,创建一个 crawler 实例,并给它指定一个数据库名称(就像你上面举的例子)。

29

我想对Amadan的详细解释贡献一点自己的看法。

类是对某种“类型”的抽象描述,而对象则是这些类的具体实现,像是活生生的东西。在面向对象的世界里,有几个主要的概念,可以说是所有事物的精髓。它们是:

  1. 封装(这里不详细讲)
  2. 继承
  3. 多态

对象有一个或多个特征(也叫属性)和行为(也叫方法)。行为通常取决于特征。类定义了行为应该实现什么,但只要这个类没有被实例化成对象,它就仍然是一个抽象的可能性概念。让我用“继承”和“多态”来举个例子。

    class Human:
        gender
        nationality
        favorite_drink
        core_characteristic
        favorite_beverage
        name
        age

        def love    
        def drink
        def laugh
        def do_your_special_thing                

    class Americans(Humans)
        def drink(beverage):
            if beverage != favorite_drink: print "You call that a drink?"
            else: print "Great!" 

    class French(Humans)
        def drink(beverage, cheese):
            if beverage == favourite_drink and cheese == None: print "No cheese?" 
            elif beverage != favourite_drink and cheese == None: print "Révolution!"

    class Brazilian(Humans)
        def do_your_special_thing
            win_every_football_world_cup()

    class Germans(Humans)
        def drink(beverage):
            if favorite_drink != beverage: print "I need more beer"
            else: print "Lecker!" 

    class HighSchoolStudent(Americans):
        def __init__(self, name, age):
             self.name = name
             self.age = age

jeff = HighSchoolStudent(name, age):
hans = Germans()
ronaldo = Brazilian()
amelie = French()

for friends in [jeff, hans, ronaldo]:
    friends.laugh()
    friends.drink("cola")
    friends.do_your_special_thing()

print amelie.love(jeff)
>>> True
print ronaldo.love(hans)
>>> False

一些特征定义了人类。但每个国家的人又有所不同。所以“国籍类型”可以看作是带有附加特征的人类。“美国人”是一种“人类”,从人类这个类型(基类)继承了一些抽象的特征和行为:这就是继承。因此,所有人类都能笑和喝酒,所以所有子类也能这样!这就是继承(2)。

因为它们都是同一种类型(基类:人类),所以有时可以互换:见最后的for循环。但它们会展现出个别的特征,这就是多态(3)。

每个人都有自己喜欢的饮料,但每个国家的人往往偏爱某种特定的饮料。如果你从人类这个类型中子类化一个国籍,你可以重写继承来的行为,就像我在上面用drink()方法演示的那样。但这仍然是在类的层面上,因此它仍然是一个概括。

hans = German(favorite_drink = "Cola")

实例化了德国人这个类,我在开始时“改变”了一个默认特征。(但如果你调用 hans.drink('Milk'),他仍然会说“我需要更多的啤酒”——这显然是个bug……或者如果我是大公司的员工,我可能会把这称为一个功能。;-)!)

例如,德国人(hans)的特征通常在实例化时通过构造函数(在Python中是__init__)来定义。这是你将类变成对象的时刻。你可以说是给一个抽象概念(类)注入了生命,通过填充个别特征而成为一个对象。

但因为每个对象都是类的一个实例,它们共享一些基本的特征类型和行为。这是面向对象概念的一个主要优点。

为了保护每个对象的特征,你需要进行封装——这意味着你尽量将行为和特征结合在一起,并使其难以从对象外部进行操作。这就是封装(1)。

325

根据你所写的内容,你缺少一个关键的理解:类和对象之间的区别。__init__ 不是用来初始化一个类的,而是用来初始化一个类的实例或者说对象。每只狗都有颜色,但作为一个类的狗没有颜色。每只狗有四条或更少的腿,但狗这个类没有腿。类是一个对象的概念。当你看到 Fido 和 Spot 时,你会认出它们的相似之处,它们都是狗。这就是类的意思。

当你说

class Dog:
    def __init__(self, legs, colour):
        self.legs = legs
        self.colour = colour

fido = Dog(4, "brown")
spot = Dog(3, "mostly yellow")

你是在说,Fido 是一只棕色的狗,有四条腿,而 Spot 是一只有点残疾的狗,主要是黄色的。__init__ 函数被称为构造函数或初始化器,当你创建一个新类的实例时,它会自动被调用。在这个函数中,新创建的对象会被赋值给参数 selfself.legs 是一个属性,叫做 legs,它属于变量 self 中的对象。属性有点像变量,但它们描述的是对象的状态,或者是对象可以执行的特定操作(函数)。

但是,请注意,你并没有为狗的本质(doghood)设置 colour - 它是一个抽象概念。类上有一些属性是有意义的。例如,population_size 就是一个这样的属性 - 你不能去数 Fido,因为 Fido 永远都是一只。你可以去数狗的数量。假设世界上有两亿只狗。这是狗这个类的属性。Fido 和 Spot 与这个两亿的数字没有关系。这个属性叫做“类属性”,与上面提到的“实例属性” colourlegs 相对。

现在,聊点与编程更相关的内容。正如我下面所写的,添加东西的类并不合理 - 它是什么类的?Python 中的类由不同数据的集合组成,这些数据的行为相似。狗的类由 Fido 和 Spot 以及其他 199999999998 只类似的动物组成,它们都在路灯杆上撒尿。那么,添加东西的类由什么组成?它们有什么固有的数据可以区分?它们共享什么操作?

不过,数字……这些是更有趣的主题。比如,整数。它们的数量比狗多得多。我知道 Python 已经有整数了,但我们可以假装不懂,再“实现”一次它们(通过作弊,使用 Python 的整数)。

所以,整数是一个类。它们有一些数据(值),还有一些行为(“把我加到另一个数字上”)。让我们展示一下:

class MyInteger:
    def __init__(self, newvalue):
        # imagine self as an index card.
        # under the heading of "value", we will write
        # the contents of the variable newvalue.
        self.value = newvalue
    def add(self, other):
        # when an integer wants to add itself to another integer,
        # we'll take their values and add them together,
        # then make a new integer with the result value.
        return MyInteger(self.value + other.value)

three = MyInteger(3)
# three now contains an object of class MyInteger
# three.value is now 3
five = MyInteger(5)
# five now contains an object of class MyInteger
# five.value is now 5
eight = three.add(five)
# here, we invoked the three's behaviour of adding another integer
# now, eight.value is three.value + five.value = 3 + 5 = 8
print eight.value
# ==> 8

这有点脆弱(我们假设 other 会是 MyInteger),但我们现在可以忽略它。在真实代码中,我们不会这样做;我们会测试以确保,甚至可能会强制转换(“你不是整数?天哪,你有 10 纳秒的时间变成一个!9... 8....”)

我们甚至可以定义分数。分数也知道如何加自己。

class MyFraction:
    def __init__(self, newnumerator, newdenominator):
        self.numerator = newnumerator
        self.denominator = newdenominator
        # because every fraction is described by these two things
    def add(self, other):
        newdenominator = self.denominator * other.denominator
        newnumerator = self.numerator * other.denominator + self.denominator * other.numerator
        return MyFraction(newnumerator, newdenominator)

分数的数量甚至比整数还要多(其实并不是,但计算机不知道)。让我们做两个:

half = MyFraction(1, 2)
third = MyFraction(1, 3)
five_sixths = half.add(third)
print five_sixths.numerator
# ==> 5
print five_sixths.denominator
# ==> 6

你实际上并没有在这里声明任何东西。属性就像一种新的变量。普通变量只有一个值。假设你写 colour = "grey"。你不能在同一个地方再有一个名为 colour 的变量,它的值是 "fuchsia"

数组在一定程度上解决了这个问题。如果你说 colour = ["grey", "fuchsia"],你就把两种颜色堆叠到这个变量里,但你通过它们的位置来区分它们(在这个例子中是 0 或 1)。

属性是绑定到对象的变量。就像数组一样,我们可以在不同的狗上有很多 colour 变量。所以,fido.colour 是一个变量,而 spot.colour 是另一个。第一个绑定到变量 fido 中的对象;第二个绑定到 spot。现在,当你调用 Dog(4, "brown")three.add(five) 时,总会有一个隐形的参数,它会被赋值给参数列表前面的那个多余的部分。这个参数通常叫做 self,它会得到点前面对象的值。因此,在 Dog 的 __init__(构造函数)中,self 将是新 Dog 的样子;在 MyIntegeradd 中,self 将绑定到变量 three 中的对象。因此,three.valueself.valueadd 内部是同一个变量。

如果我说 the_mangy_one = fido,我将开始用另一个名字来指代名为 fido 的对象。从现在起,fido.colourthe_mangy_one.colour 是完全相同的变量。

所以,__init__ 里面的东西。你可以把它们想象成在狗的出生证明上记录的东西。单独的 colour 是一个随机变量,可以包含任何东西。fido.colourself.colour 就像狗的身份表上的一个表单字段;而 __init__ 就是第一次填写这个表单的职员。

这样说清楚了吗?

编辑:扩展一下下面的评论:

你是指一系列对象,对吧?

首先,fido 实际上并不是一个对象。它是一个变量,目前包含一个对象,就像当你说 x = 5 时,x 是一个变量,当前包含数字五。如果你后来改变主意,你可以做 fido = Cat(4, "pleasing")(只要你创建了一个 Cat 类),从那时起 fido 就会“包含”一个猫对象。如果你做 fido = x,它将包含数字五,而不是动物对象。

一个类本身并不知道它的实例,除非你特意写代码来跟踪它们。例如:

class Cat:
    census = [] #define census array

    def __init__(self, legs, colour):
        self.colour = colour
        self.legs = legs
        Cat.census.append(self)

这里,censusCat 类的一个类级属性。

fluffy = Cat(4, "white")
spark = Cat(4, "fiery")
Cat.census
# ==> [<__main__.Cat instance at 0x108982cb0>, <__main__.Cat instance at 0x108982e18>]
# or something like that

请注意,你不会得到 [fluffy, sparky]。这些只是变量名。如果你想让猫本身有名字,你必须为名字创建一个单独的属性,然后重写 __str__ 方法来返回这个名字。这个方法的作用是描述如何将对象转换为字符串,比如在打印时。

撰写回答