为什么在Python类中使用__init__?
我在理解类的初始化时遇到了一些困难。
类的意义是什么,我们应该在里面包含什么呢?写类和写函数是不是需要不同的思维方式?我原本以为可以直接写函数,然后把它们放进一个类里,这样就可以重复使用。这样做可以吗?
这里有一个例子:
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 个回答
这只是为了初始化实例的变量。
比如说,创建一个 crawler
实例,并给它指定一个数据库名称(就像你上面举的例子)。
我想对Amadan的详细解释贡献一点自己的看法。
类是对某种“类型”的抽象描述,而对象则是这些类的具体实现,像是活生生的东西。在面向对象的世界里,有几个主要的概念,可以说是所有事物的精髓。它们是:
- 封装(这里不详细讲)
- 继承
- 多态
对象有一个或多个特征(也叫属性)和行为(也叫方法)。行为通常取决于特征。类定义了行为应该实现什么,但只要这个类没有被实例化成对象,它就仍然是一个抽象的可能性概念。让我用“继承”和“多态”来举个例子。
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)。
根据你所写的内容,你缺少一个关键的理解:类和对象之间的区别。__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__
函数被称为构造函数或初始化器,当你创建一个新类的实例时,它会自动被调用。在这个函数中,新创建的对象会被赋值给参数 self
。self.legs
是一个属性,叫做 legs
,它属于变量 self
中的对象。属性有点像变量,但它们描述的是对象的状态,或者是对象可以执行的特定操作(函数)。
但是,请注意,你并没有为狗的本质(doghood)设置 colour
- 它是一个抽象概念。类上有一些属性是有意义的。例如,population_size
就是一个这样的属性 - 你不能去数 Fido,因为 Fido 永远都是一只。你可以去数狗的数量。假设世界上有两亿只狗。这是狗这个类的属性。Fido 和 Spot 与这个两亿的数字没有关系。这个属性叫做“类属性”,与上面提到的“实例属性” colour
或 legs
相对。
现在,聊点与编程更相关的内容。正如我下面所写的,添加东西的类并不合理 - 它是什么类的?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 的样子;在 MyInteger
的 add
中,self
将绑定到变量 three
中的对象。因此,three.value
和 self.value
在 add
内部是同一个变量。
如果我说 the_mangy_one = fido
,我将开始用另一个名字来指代名为 fido
的对象。从现在起,fido.colour
和 the_mangy_one.colour
是完全相同的变量。
所以,__init__
里面的东西。你可以把它们想象成在狗的出生证明上记录的东西。单独的 colour
是一个随机变量,可以包含任何东西。fido.colour
或 self.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)
这里,census
是 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__
方法来返回这个名字。这个方法的作用是描述如何将对象转换为字符串,比如在打印时。