1. 在类的外部给对象增加属性的隐患
我们可以在一个类的外部通过对象名点(.) 的方式非常方便的给对象增加一个属性, 但这种方式存在隐患, 因为程序在运行的时候, 找到属性就可以正常执行(从上到下顺序地找), 找不到属性就会直接报错.
class Cat:
def eat(self):
# 哪一个对象调用的方法,self 就是哪一个对象的调用
print("%s 爱吃鱼 " % self.name)
def drink(self):
print("%s 要喝水" % self.name)
#创建猫对象
tom = Cat()
# 可以使用 .属性名 在利用赋值语句就可以了
tom.name = "Tom" #先设置name 属性, 后面调用方法才不会报错
#调用方法
tom.eat()
tom.drink()
输出结果
如果把设置name 属性这行代码放在调用方法地末尾(也就是将第15行代码挪动到第19行), 程序就不能正常执行了, 会报错. 执行到第5行的时候, 会告诉我们 Cat() 对象没有name 这个属性.
代码:
class Cat:
def eat(self):
# 哪一个对象调用的方法,self 就是哪一个对象的调用
print("%s 爱吃鱼 " % self.name)
def drink(self):
print("%s 要喝水" % self.name)
#创建猫对象
tom = Cat()
# 可以使用 .属性名 在利用赋值语句就可以了
#调用方法
tom.eat()
tom.drink()
tom.name = "Tom" #先调用方法, 后设置name 属性 会报错
输出结果:
报错信息: 猫对象没有name 属性
原因是Python 程序是从上到下顺序执行的, 当在第一行发现一个class 关键字之后, 解释器并不会立即执行, 然后来到第12行等号右侧在内存中创建了一个Cat 对象, 让tom 这个变量对 Cat() 对象进行了一个引用,
然后再看我们之前注释的第15行代码, 这句代码再Cat 对象中增加了一个name 属性, 增加完成之后, 才会调用eat 方法, 在调用eat 方法的时候, 第一个参数self 表示哪一个对象的调用, self 就是哪一个对象的引用.
在第五行, 我们通过self.name 把 Cat() 对象中的name 属性做了一个输出.
注释掉第15行后, 创建完Cat() 对象就直接调用eat 方法, 那么给 Cat() 对象添加name 属性的第15行代码就没有执行, 既然这行代码没有执行, Cat() 对象中就没有name 属性, 既然 Cat() 对象中没有name属性, 在执行eat 方法时, 来尝试输入self.name 就会报错. 这就是把第15 行代码移到第18 行后出现的情况.
不推荐在一个类的外部给对象增加属性,因为程序在运行的时候, 找到属性就可以正常执行,找不到属性就会直接报错,这种代码在编写起来就比较困难了. 一个类创建出来的对象应该具有哪些属性?这些代码应该放在哪里?这些代码应该同样封装在类的内部,因为由哪一个类创建出来的对象应该具有哪些属性, 这件事情是应该有这个类负责的,所以这个类创建出来的对象具体包含有哪些属性的代码,同样也应该封装在这个类的内部.
2. 创建对象时自动调用初始化方法
当我们使用类名再跟上一对小括号()创建一个对象, python 解释器会自动帮我们做两个事情, 第一件, 当我们使用类名() 想要创建一个对象时, python 解释器会首先在内存中为对象分配一个小格子, 保证这个小格子能够有足够的空间存放下我们要创建的对象, 第二件, 当这个小格子创建完成之后, 再会调用另外一个初始化方法, 在初始化方法中, 给对象的所有属性设置初始值.
给属性设置初始值的方法, 我们就把它叫做初始化方法.
在python 中, 对象的初始化方法有一个专门的名字, 格式: __init__ 方法, 这种格式的方法是中针对对象提供的内置方法, 我们是可以直接使用的.
在python 中, 初始化方法就是专门用来定义一个类时, 指定这个类具体有哪些属性的, 这个就是初始化方法的作用.
在使用类名() 创建对象时, 会自动调用__init__ 这个初始化方法.
先使用一个class 关键字来定义一个Cat 类, 当类名准备完成, 如果我们想要使用__init__方法, 同样也是使用 def 关键字, def __init__ , 这个方法的第一个参数同样是self, 然后使用print 函数进行输出print ("这是一个初始化方法"), 初始化方法准备完成后, 我们就在代码的下方来创建一个猫对象, 先给猫(变量)起个名字, 叫tom, 然后使用Cat () 创建一个猫对象, tom = Cat(),
代码写完, 就运行一下程序, 控制台输出了"这是一个初始化方法",
但上面编写的代码并没有主动调用__init__ 方法, 当使用类名加括号的时候, Cat (), 会自动帮我们调用这个初始化方法__init__,
当我们使用类名加上一对括号, 创建对象时, Python解释器会自动帮我们做两件事情,第1件事情分配空间,第2件事情就是设置初始值,而设置初始值的方法就是Python中的对象初始化方法__init__ ,这个方法的名字非常的固定. __init__ 方法在Python中的作用, 就是在定义一个类的时候, 来指定这个类到底具有哪些属性.
3. 在初始化方法中定义属性
如果我们希望使用类名创建出来的对象,默认就拥有某个属性,我们就可以在初始化方法内部使用self点属性名,然后使用赋值语句, 就可以给这个属性设置初始值,并且定义一个属性了,使用这种方式定义属性之后,我们再使用这个类创建出来的对象就会都拥有这个属性了.
使用类名()创建对象的时候, 会自动调用初始化方法__init__
把光标放在代码的末尾,使用print函数输出一下汤姆这个变量,然后敲一个点,现在敲一个name,pycharm 会给我们有智能提示.
选中name 回车,当我们的光标放在name上,而初始化方法内部的name, 同样也有一个高亮的背景,
运行一下程序, 控制台输出了汤姆的名字,就说明我们现在创建的猫对象就已经拥有了name 的属性, 而属性的初始值叫Tom.
画一个示意图,描述一下代码是怎么执行的,python 的解释器是从上向下顺序执行,代码前面那位不会立即执行,当代码来到了第11行要准备创建一个猫对象, 一个类名加一对括号, Python解释器会帮我们做两件事情,第1件事情会在内存中为猫对象tom 分配一个足够大的空间,这个空间呢有一个对应的地址,譬如0x1234,
当这个空间分配完成之后才会进行第二件事情, 调用初始化方法,在初始化方法中self这个参数, 就会指向刚刚创建的猫对象tom 在内存中的地址0x1234,在执行初始化方法的时候,同样是从上向下顺序执行,当执行到第8句,要给self这个参数指向的猫对象tom 添加一个name 属性, 那么我们就在刚刚创建的猫对象tom 中添加一个name 属性,并且指令一下初始值叫做Tom,因此我们在使用print 函数把tom 的name 属性打印的时候,就会把tom的name 属性的初始值Tom输出到控制台.
这个就是在初始化方法中定义属性的方式,在self点后面跟上属性名,然后使用赋值语句就可以定义属性了,同时我们是在初始化方法中定义的属性,这种方式定义的属性在编写代码时是有智能提示的.
4. 使用参数设置属性初始值
介绍一下怎么样对初始化方法进行改造,之所以要对初始化方法进行改造啊,是因为现在完成的代码还存在一些缺陷.
我们确实是在初始化方法中为猫类增加了一个name 属性,并且指定了一个初始值,但是我们指定的初始值是一个固定的"Tom"字符串,这个就意味着我们在代码中每一次使用猫类Cat()创建出来的猫对象,默认的名字都叫做"Tom".
现在来定一个lazy_cat, 懒猫的变量,同样使用猫类创建一个猫对象,让lazy_cat 来调用一下吃鱼的方法,另外lazy_cat 现在调用吃鱼的方法在控制台输出的name同样也是"Tom".
class Cat:
def __init__(self):
print("这是一个初始化方法")
#self.属性名 = 属性的初始值
self.name = "Tom"
def eat(self):
print("%s 爱吃鱼 " % self.name)
# 使用类名()
# 创建对象的时候, 会自动调用初始化方法__init__
lazy_cat = Cat()
lazy_cat.eat()
运行结果:
我们现在完成的代码,并不希望把猫的名字固定死, 对吧,那怎么样解决这个问题呢?我们在学习函数时,当我们不希望函数中某一个值被固定死,我们可以给函数增加一个形参,我们用这个形参来替换一下函数内部被固定死的值, 对吧,这样呢就可以让函数内部的代码灵活起来,同时增加了形参之后,我们在调用函数时,应该以实参的方式把具体希望在函数内部表达的值传递进来.
所谓方法就是封装在一个类的内部的函数,具体的语法跟我们之前学习的函数是完全一模一样的. 既然我们不希望"Tom"这个值被固定死,可以给初始化的方法进行一个改造,给初始化方法呢,增加一个形参.
现在在self后面增加一个逗号,然后给形参起个名字new_name,形参确定好了之后,然后在下方使用self点同样定一个name的属性,定义完成, 我们就把new_name这个形参赋值给self.name,赋值完成, 当我们在使用Cat这个类,在Pycharm提示我们应该传入一个new_name这个参数.
把光标放在tom = Cat()小括号内部写上"Tom",然后再在lazy_cat = Cat()里写上大懒猫,
class Cat:
def __init__(self, new_name):
print("这是一个初始化方法")
#self.属性名 = 属性的初始值
#self.name = "Tom"
self.name = new_name
def eat(self):
print("%s 爱吃鱼 " % self.name)
# 使用类名()
# 创建对象的时候, 会自动调用初始化方法__init__
tom = Cat("Tom")
print(tom.name)
lazy_cat = Cat("大懒猫")
lazy_cat.eat()
两个参数传递完成,运行一下程序,运行结果:
经过对初始化方法的改造, 我们增加了一个形参就可以做到,在创建这个对象时,我们把对象一些特有的属性以参数的形式传递到了初始化方法的内部,这样创建出来的对象就更加灵活多样了.
在开发中啊,如果希望在创建对象的同时,就直接设置对象的某些属性,这个时候呢,我们就可以对初始化方法进行改造,所谓改造啊,就是把希望传递的属性值,定义成初始化方法的参数,然后在方法内部我们使用self点定义属性的时候,赋值语句的右侧我们就使用新定义的形参.
同时在创建对象时,我们在类名后面的小括号中啊,就依次把希望设置的属性值,逐一放置在小括号内部, 属性与属性之间,使用逗号分割就可以,这样就是对初始化方法的改造,一句话讲,初始化方法改造之后,我们在使用这个类创建出来的对象,就可以直接拥有我们在实参中传递的属性值了.