为什么在Python类中使用__init__?


124

我在理解类的初始化时遇到了麻烦。

它们的意义何在?我们如何知道其中包含什么?用类编写与创建函数相比是否需要不同的思维方式(我认为我可以只创建函数,然后将它们包装在类中,以便我可以重用它们。这行得通吗?)

这是一个例子:

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__尝试阅读别人的代码时遇到了很多类,但是我不理解创建它们的逻辑。


1
初始化的故事是……等等,等等,等等。构造函数-析构函数,但是没有析构函数,因为可以进行垃圾收集。
MisterGeeky

Answers:


289

根据您所写的内容,您缺少一个关键的理解:类和对象之间的区别。__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有点of弱,而且大多是黄色的。该__init__函数称为构造函数或初始化器,当您创建类的新实例时会自动调用该函数。在该函数内,将新创建的对象分配给parameter self。表示法self.legslegs变量中称为对象的属性self。属性有点像变量,但是它们描述对象的状态或对象可用的特定操作(功能)。

但是,请注意,您并没有设置colour狗狗身份-这是一个抽象概念。有一些对类有意义的属性。例如,population_size就是这样的一种-计算Fido没什么意义,因为Fido总是一个。数狗确实很有意义。让我们说世界上有2亿只狗。这是Dog类的属性。菲多(Fido)与2亿数字无关,与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)来区分它们。

属性是绑定到对象的变量。像数组一样,我们可以在不同的dogs上有很多colour变量。因此,是一个变量,但是是另一个变量。第一个绑定到变量内的对象; 第二,。现在,当您调用或时,将始终有一个不可见的参数,该参数将分配给参数列表前面悬空的多余参数。它通常称为,它将在点之前获取对象的值。因此,在狗的(构造函数)内部,新狗将成为什么样的狗。中的,将绑定到变量中的对象。从而,fido.colourspot.colourfidospotDog(4, "brown")three.add(five)self__init__selfMyIntegeraddselfthreethree.value将在外部与addself.value内部相同add

如果我说the_mangy_one = fido,我将开始引用fido另一个名称的对象。从现在开始,fido.colour变量与完全相同the_mangy_one.colour

所以,里面的东西__init__。您可以将它们视为在Dog的出生证明中注明的内容。colour本身是一个随机变量,可以包含任何内容。fido.colourself.colour类似于“狗的身份证”上的表格字段;并且__init__是店员填充它的第一次。

更清晰吗?

编辑:扩展下面的评论:

您的意思是列出对象,不是吗?

首先,fido实际上不是对象。它是一个变量,这是目前包含一个对象,只是当你说喜欢x = 5x是目前包含数字五个变量。如果您以后改变主意,则可以fido = Cat(4, "pleasing")(只要创建了class Catfido就可以执行操作,此后将“包含” cat对象。如果这样做fido = x,它将包含数字5,而不是动物对象。

除非您专门编写代码来跟踪它们,否则类本身并不知道其实例。例如:

class Cat:
    census = [] #define census array

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

census是class的类级属性Cat

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__方法以返回该名称。此方法的用途(即,像add或那样,是类绑定函数__init__)的目的是描述如何将对象转换为字符串,就像将其打印出来一样。


7
哇,谢谢。.这实际上对我来说很有意义,因此,凡是能使它变得真实的东西,我都需要在init函数中预先声明。在这种情况下,狗具有腿和颜色。例如,如果我创建了一个将两个数字相加的类,则将声明self.firstnumber和self.secondnumber,然后稍后在该类中进行firstnumber + secondnumber的获取答案?
Lostsoul 2011年

1
有点儿。你可以那样做。但是,仅仅增加一些内容而创建一个类几乎没有任何意义。类通常使用行为来实现数据-纯行为只是函数。我将用一些相关的内容来扩展答案。稍等一会。
阿玛丹

3
感谢您的惊人回答。我现在了解并理解了课堂的力量。对不起,如果听起来很蠢。您就是我,我意识到我可以同时对数据进行排序并维护许多不同事物的状态(而我只能跟踪尽可能多的变量,也可以通过循环跟踪更多变量)。这么说,我需要弄清楚每只狗的平均腿数?有没有办法检索我用一个类创建的所有对象的列表,这样我就可以开始计算了?还是我还要维护我创建的类的列表(即[fido,spot])
Lostsoul 2011年

23

为我的阿玛丹详尽解释贡献我的5美分。

其中类是抽象的“类型”描述。对象是它们的实现:呼吸的活物。在面向对象的世界中,有一些基本思想几乎可以称为一切的本质。他们是:

  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")

实例化类German,我从一开始就“更改”了默认特征。(但是,如果您打电话给hans.drink('Milk'),他仍然会打印“我需要更多啤酒”-一个明显的错误……或者,如果我将成为更大公司的雇员,那就是我所说的功能。 ;-)!)

通常__init__在实例化时通过构造函数(在python:中)定义类型(例如,德语(hans))的特征。这是定义类以成为对象的地方。您可以通过将个性化特征填充为一个抽象对象(类),将呼吸生活说成一个抽象概念(类)。

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

为了保护每个对象的特征,可以将它们封装在一起-意味着您尝试将行为和特征耦合在一起,并使其难以从对象外部进行操纵。那就是封装(1)


5

它只是初始化实例的变量。

例如crawler,使用特定的数据库名称创建一个实例(来自上述示例)。


很抱歉,我真的不明白这意味着什么。.在上面的示例中..开发人员不能只在他的主要代码中添加“ left = foo”等
吗?

您的意思是该功能的默认值? 如果在创建时未指定参数left=NoneNone则将left初始化为left
jldupont 2011年

我认为它开始变得有意义..就像您必须在Java“ String left”中预先声明变量一样吗?那么一旦将其初始化为类,就可以操纵值了吗?与函数相比,这有点令人困惑,因为我可以将值发送给函数,而无需提前初始化任何东西。
Lostsoul 2011年

1
@Lostsoul:left = foo会工作-一次。课堂的重点是对每个不同的事物做出明智的选择crawler。类不是函数,也不是可以与函数进行比较的东西(嗯,直到您变得更高级并进入函数式编程,但这现在才使您感到困惑)。阅读我的答案,了解实际上是什么课-您仍然没有得到。
阿玛丹

4

__init__如果要正确初始化实例的可变属性,似乎需要在Python中使用。

请参见以下示例:

>>> class EvilTest(object):
...     attr = []
... 
>>> evil_test1 = EvilTest()
>>> evil_test2 = EvilTest()
>>> evil_test1.attr.append('strange')
>>> 
>>> print "This is evil:", evil_test1.attr, evil_test2.attr
This is evil: ['strange'] ['strange']
>>> 
>>> 
>>> class GoodTest(object):
...     def __init__(self):
...         self.attr = []
... 
>>> good_test1 = GoodTest()
>>> good_test2 = GoodTest()
>>> good_test1.attr.append('strange')
>>> 
>>> print "This is good:", good_test1.attr, good_test2.attr
This is good: ['strange'] []

在Java中,这是完全不同的,在Java中,每个属性都使用新值自动初始化:

import java.util.ArrayList;
import java.lang.String;

class SimpleTest
{
    public ArrayList<String> attr = new ArrayList<String>();
}

class Main
{
    public static void main(String [] args)
    {
        SimpleTest t1 = new SimpleTest();
        SimpleTest t2 = new SimpleTest();

        t1.attr.add("strange");

        System.out.println(t1.attr + " " + t2.attr);
    }
}

产生我们直觉上期望的输出:

[strange] []

但是,如果声明attrstatic,它将像Python一样运行:

[strange] [strange]

3

以您的汽车示例为例:当您获得汽车时,您只是没有得到随机的汽车,我的意思是,您选择颜色,品牌,座位数等。并且有些东西在没有您选择的情况下也是“初始化”的为此,例如车轮数或注册号。

class Car:
    def __init__(self, color, brand, number_of_seats):
        self.color = color
        self.brand = brand
        self.number_of_seats = number_of_seats
        self.number_of_wheels = 4
        self.registration_number = GenerateRegistrationNumber()

因此,在该__init__方法中,您可以定义要创建的实例的属性。因此,如果我们想要一辆蓝色雷诺汽车,供2人使用,我们将进行初始化或Car诸如此类的实例:

my_car = Car('blue', 'Renault', 2)

这样,我们正在创建Car类的实例。该__init__是我们处理的特定属性(如一个colorbrand)及其产生的其他属性,如registration_number


3

类是具有特定于该对象的属性(状态,特征)和方法(功能,功能)的对象(例如,鸭子的白色和飞行能力)。

当创建一个类的实例时,可以给它一些初始的个性(状态或字符,例如她的名字和新生儿衣服的颜色)。您可以使用__init__

基本上__init__在您调用时自动设置实例特征instance = MyClass(some_individual_traits)


2

__init__函数正在设置类中的所有成员变量。因此,一旦创建了bicluster,您就可以访问该成员并获取值:

mycluster = bicluster(...actual values go here...)
mycluster.left # returns the value passed in as 'left'

查看Python文档,了解一些信息。您将希望读一本有关面向对象概念的书,以继续学习。


1
class Dog(object):

    # Class Object Attribute
    species = 'mammal'

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

在上面的示例中,我们将物种用作全局物种,因为它始终是相同的(可以说一类常数)。当您调用__init__method 时,里面的所有变量__init__都会被初始化(例如:breed,name)。

class Dog(object):
    a = '12'

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

如果您通过像下面这样调用下面来打印上面的示例

Dog.a
12

Dog('Lab','Sam','10')
Dog.a
10

这意味着它将仅在对象创建期间初始化。因此,您要声明为常量的任何内容都将其设置为全局的,并且任何更改使用的内容 __init__

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.