文章目录
1. 类
我们所说的同属一类,就隐藏着一个共识,就是同一类的事物具有共同的特征。比如我们都是中国人,中国人是一类人,具有一些共同点:黑眼睛、黑头发,黄皮肤、用筷子、会讲汉语。而这些共同点就是我区别于其他类的依据。这些特点如果细分的话可以分成两种:
- 第一种是描述事物是怎样的,如黑头发,黑眼睛,黄皮肤
- 第二种是描述事物能做什么,有哪些行为和作用。如吃饭用筷子,会讲汉语。
在编程世界中我们管第一种特征叫属性,第二种特征叫方法。
Python中每个类都有自己的属性和方法,每个实例对象都可以调用这些属性和方法。
1.1 类的创建
我们先创建一个电脑类:
class Computer:
screen=True
def start(self):
print('电脑正在开机中')
电脑类都有屏幕,属性screen为True。
开机的时候会显示:电脑正在开机中。
我们来具体看看创建类的语法:
实例方法的创建语句,和函数的定义语句很类似,唯一不同的是:实例方法中有个必须放在首位的参数self。
类名的首字母要大写,以便让我们轻松地辨认出“哦!这个是类!”
例题:创建一个中国人类,并创建一个属性和方法。
class Chinese:
eye=dark
def speak(self):
print('母语讲中文')
这样我们就创建了一个简单的类。但是如果我运行代码,不会有任何结果,也不会报错。因为我们创建了类,但是还没有调用类。
1.2 类的调用
下面我们调用刚才创建的类:
class Chinese:
eye=dark
def speak(self):
print('母语讲中文')
xiaoming=Chinese()
print(xiaoming.eye)
xiaoming.speak()
结果
结果我直接调用的时候出现了报错,说dark没有定义。那是因为dark没有用引号,当作变量处理了。
class Chinese:
eye='dark'
def speak(self):
print('母语讲中文')
xiaoming=Chinese()
print(xiaoming.eye)
xiaoming.speak()
执行结果
调用的关键在第9行代码:xiaoming = Chinese()。这个过程叫作:类的实例化,即在某个类下创建一个实例对象。至此我们创建了一个可以调用所有所属类的属性和方法的实例,语法如下:
实例名=类名()
xiaoming=Chinese()
我们来试下打印实例类型和直接打印出实例,会是什么结果呢?
class Chinese:
eye='dark'
def speak(self):
print('母语讲中文')
xiaoming=Chinese()
print(type(xiaoming)
print(xiaoming)
我们看到xiaoming属于Chinese类,xiaoming是一个Chinese类对象,对象的内存地址是:0x000001A3D45694C0
当实例my_computer一被创建出来,就可以调用类中的属性和方法。一句话概括就是:类有的实例都会有。
1.3 属性和方法的调用
语法:实例名.属性 和 实例名.方法
例题:
class Chinese:
eye='dark'
def speak(self):
print('母语讲中文')
xiaoming=Chinese()
print(xiaoming.eye)
xiaoming.speak()
实例在调用类的属性的时候,解析器查找的顺序是先在本实例对象下去查找,如果没有再去所属类对象中查找,如果本实例对象中有该属性,就输出该属性,如果没有输出所属类对象的该属性名的属性值,如果还没有则会报错。
例题:
class Chinese:
eye='dark'
def speak(self):
print('母语讲中文')
xiaoming=Chinese()
xiaogang=Chinese()
xiaogang.eye='双眼皮'
xiaohong=Chinese()
print(xiaoming.eye,xiaogang.eye,xiaohong.eye)
如果去我调用一个当前实例对象和该类对象都没有的属性就会报错,如:
class Chinese:
eye='dark'
def speak(self):
print('母语讲中文')
xiaohong=Chinese()
print(xiaohong.hair)
如果当前实例和所属类中都有所要调用的属性呢?就会照着我们刚才所说的顺序解析器去查找,先在当前实例对象中查找,若有就调用,没有再去所属类对象属性中查找。若都有,遵循就近原则。
所属类都是黑头发,但是小红的是红头发,估计是染了,开个玩笑。如果要调用小红的头发颜色,当然是她自己的红头发了。如果不知道她是什么颜色的头发,那么才会调用所属类中的黑头发。
总结说明:
- 类对象和实例对象都可以保存属性和方法
- 如果一个属性或者方法是所有实例所共享的,则应保存在类对象中
- 如果一个属性或者方法是某个实例所独有的,则应该保存在实例对象中
- 一般情况下属性保存到实例对象中,因为属性都是独立拥有的居多
- 一般情况下方法保存到类对象中
- 归纳一下三个步骤就是:创建一个类 —— 类的实例化 —— 用实例调用类的属性和方法。
再啰嗦一句:类中创建的属性和方法可以被其所有的实例调用,而且,实例的数目在理论上是无限的。如上面的例子中,我就创建了xiaoming=Chinese,xiaohong=Chinese,xiaogang=Chinese三个实例。因此,类就像一个“实例工厂”,因其为所有实例提供了一套蓝图(即预先设定好有什么属性和方法)。
2. 特殊参数: self
在前面定义一个类的方法的时候,大家注意到后面括号里有一个参数self,这个参数定义的时候必不可少,然而调用的时候却必须忽略。这是为什么呢?下面我们慢慢讲解。
2.1 self参数的作用
正式揭秘特殊参数self的作用:self会接收实例化过程中传入的数据,当实例对象创建后,实例便会代替 self,在代码中运行。换言之,self 是所有实例的替身,“替身”是什么意思呢?我们来看一个例子。刚刚我们列举的类方法都只有一个self参数,实际上和一般函数一样,类的方法也可以设置多个参数:
class Chinese:
name='Li Guanghui'
def speak(self, someone):
print(someone+'是中国人')
person1=Chinese()
print(person1.name)
person1.speak()
报错提示,speak()方法里少传了一个参数。我们传入一个人名。
class Chinese:
name='Li Guanghui'
def speak(self, someone):
print(someone+'是中国人,说汉语。')
person1=Chinese()
print(person1.name)
person1.speak('Li Guanghui')
self调用时要忽略,‘Li Guanghui’传给参数someone
这样写虽然没错,但实际上是多此一举,因为只要在say方法内部调用类属性’Li Guanghui’,就可以实现同样的功能,不必重复传参。
怎么在方法内部调用类属性呢?你可能会想着这样写:
class Chinese:
name='Li Guanghui'
def speak(self):
print(name+'是中国人,会说汉语。')
person1=Chinese()
person1.speak()
但这样写会报错。
person1是Chinese这个类的实例,不能访问到变量name。还记得我们刚说的,如果要在类的外部调用类属性,我们得先创建一个实例,再用实例名.属性的格式调用吗?那么如果想在类的内部调用类属性,而实例又还没创建之前,我们就需要有个变量先代替实例接收数据,这个变量就是参数self。
正确的写法应该是这样子的:
class Chinese:
name='Li Guanghui'
def speak(self):
print(self.name+'是中国人,会说汉语。')
person1=Chinese()
person1.speak()
当最后一行代码运行时,实例person1会像参数一样传给self,替换掉self,第67行的self.name等价于person1.name。person1.name就相当于调用了类属性name(即’Li Guanghui’),然后跑完整个方法。
他的作用相对于(对照上面的代码看):
class Chinese:
name='Li Guanghui'
def speak(person1):
print(person1.name+'是中国人,会说汉语。')
person1=Chinese()
person1.speak()
而这里不是self其实就相当于自己的,就是实例对象自己的参数,后面无论创建什么实例,self都会被实例名所替代。
可见,self的作用相当于先给实例占了个位置,等到实例创建好就“功成身退,退位让贤”。
2.2 self参数的进一步理解
下面我想通过一个例子加深对self参数的认识。
例题:定义一个类,实现不同的实例调用类属性的时候,有不同的属性。
class Chinese:
hair='黑色'
def speak(self):
print('我的头发颜色是黑色')
xiaoming=Chinese()
xiaohong=Chinese()
xiaoming.speak()
xiaohong.speak()
如果代码如上去写的话输出的是这样的:
结果都是“我的头发颜色是黑色的”,而我想实现的是小名的头发是黑色的,小红的头发是红色的。
我想到用格式化字符串知识这样处理:
class Chinese:
hair='黑色'
def speak(self):
print('我的头发颜色是%s'%hair)
xiaoming=Chinese()
xiaohong=Chinese()
xiaoming.speak()
xiaohong.speak()
结果报错了,说hair这个变量没有定义。我们记得在函数中,函数是可以读取外部定义的变量的,外部不能读取函数内部定义的变量。但是在类中是不行的。在类中是不能直接调用外部的变量的,必须用实例名.属性名的语法去调用。所以:
class Chinese:
hair='黑色'
def speak(self):
print('我的头发颜色是%s'%xiaoming.hair)
xiaoming=Chinese()
xiaohong=Chinese()
xiaoming.speak()
xiaohong.speak()
这次没有报错,但全是黑色。于是我想到定义实例自己的属性:
class Chinese:
hair='黑色'
def speak(self):
print('我的头发颜色是%s'%xiaoming.hair)
xiaoming=Chinese()
xiaoming.hair='黑色'
xiaohong=Chinese()
xiaohong.hair='红色'
xiaoming.speak()
xiaohong.speak()
但是问题又出现了,我传递参数的时候,只能一次写一个人的名字,要么xiaoming 要么xiaohong。有没有一个方法,当实例改变时,这个参数也跟着改变呢?这个时候我关注到self参数,它会不会满足我的期待呢?我用不同的类的实例调用,然后打印一下self。
class Chinese:
hair='黑色'
def speak(self):
print(self)
#print('我的头发颜色是%s'%xiaoming.hair)
xiaoming=Chinese()
#xiaoming.hair='黑色'
xiaohong=Chinese()
#xiaohong.hair='红色'
xiaoming.speak()
xiaohong.speak()
很惊喜的发现,当用不同的实例调用speak()方法的时候,self的内存地址是不一样的,这说明它时两个完全不同的对象。我们再进一步研究self和实例对象的关系。
class Chinese:
hair='黑色'
def speak(self):
print(self)
xiaoming=Chinese()
print(xiaoming)
我们发现对象xiaoming的内存地址和self的内存地址是完全一样的,说明是同一个对象。那xiaohong调用的时候呢?
class Chinese:
hair='黑色'
def speak(self):
print(self)
xiaoming=Chinese()
xiaohong=Chinese()
print(xiaoming)
xiaoming.speak()
print(xiaohong)
xiaohong.speak()
我们发现当实例对象不同的时候,self对象和实例对象保持一致。
通过研究我们发现,如果xiaoming调用self就是xiaoming,如果xiaohong调用self就是xiaohong。那么,我们就可以直接这样处理上面的问题:
class Chinese:
hair='黑色'
def speak(self):
print('我的头发颜色是%s'%self.hair)
xiaoming=Chinese()
xiaoming.hair='黑色'
xiaohong=Chinese()
xiaohong.hair='红色'
xiaoming.speak()
xiaohong.speak()
这就是实现了我们的目的。此处应该有鲜花。
综上,所以我们说self代表的是类的实例本身,方便数据的流转。对此,我们需要记住两点:
- 第一点:只要在类中用def创建方法时,就必须把第一个参数位置留给 self,并在调用方法时忽略它(不用给self传参)
- 第二点:当在类的方法内部想调用类属性或其他方法时,就要采用self.属性名或self.方法名的格式。
好,self这个神奇的参数就讲解到这里。对初学者来说,确实不是那么容易理解,如果不熟悉,可以多多温习,做做课后练习巩固一下哦。
3. 初始化方法(构造函数)
定义初始化方法的格式是def init(self),是由init加左右两边的【双】下划线组成( initialize “初始化”的缩写)。
初始化方法的作用在于:当每个实例对象创建时,该方法内的代码无须调用就会自动运行。
3.1 初始化方法的第一种引入
例子:
class Chinese:
def __init__(self):
print('很高兴遇见你,我是初始化方法')
person1=Chinese()
是不是很神奇?我们只是创建了实例,还没有调用,初始化方法就自动执行了!
利用这个特性,在编写习惯上,我们会在初始化方法内部完成类属性的创建,为类属性设置初始值,这样类中的其他方法就能直接、随时调用。我们来看个例子:
class Chinese:
def __init__(self):
self.mouth=1
self.eye=2
def speak(self):
print('我有%s张嘴,%s只眼'% (self.mouth,self.eye))
person_01=Chinese()
person_01.speak()
注意再初始化方法中创建属性的格式:
self.mouth=1
self.eye=2
变量中的self一定不能丢。
除了设置固定常量,初始化方法同样可以接收其他参数,让传入的这些数据能作为属性在类的方法之间流转。我们再来看个例子:
class Chinese:
def __init__(self,name,birth,region):
self.name=name
self.birth=birth
self.region=region
def speak(self):
print('我是%s,出生于%s年,我的出生地是%s'%(self.name,self.birth,self.region))
guanghui=Chinese('Guanghui',1982,'Henan')
guanghui.speak()
留心观察:
162行代码中,定义初始化方法的时候不仅有特殊参数,也有其他三个形参。
163-165行代码中,定义初始化变量,并把形参赋值给它们,这样传入的实参直接成为初始化变量的值。当然不一定是self.name=name,也可以self.abc=name,这个是任意的,只要后面传入变量是跟定义的一致就行了。
168行代码中,在类的实例化时记得一定要传入所需要的实参,不然会报错。
这里报错说缺少三个位置参数。
随着我们想实现的功能愈发复杂,我们会在类内部编写很多的方法,如果我们需要传入的数据能在类中长久保存并能被随时调用,初始化方法就是一个不错的解决方案。
练习:补充下面的代码以实现后面的打印结果是:你在哪里出生?我出生在河南。
class Chinese:
def
def born(self):
print('我出生在%s。' % self.hometown)
guanghui = Chinese('河南')
guanghui.born()
答案:
class Chinese:
def __init__(self,hometown):
self.hometown=hometown
def born(self):
print('我出生在%s。' % self.hometown)
guanghui = Chinese('河南')
guanghui.born()
3.2 初始化方法的另一种引入
例题:
class Chinese:
name='葫芦娃'
def speak(self):
print('大家好,我是%s'%self.name)
p_01=Chinese()
p_01.speak()
现在我想多创建几个实例对象,而且不同的实例对象的名称不同。
class Chinese:
# name='葫芦娃'
def speak(self):
print('大家好,我是%s'%self.name)
p_01=Chinese()
p_01.name='钢铁侠'
p_01.speak()
p_02=Chinese()
p_02.name='蜘蛛侠'
p_02.speak()
p_03=Chinese()
p_03.name='绿巨人'
p_03.speak()
p_04=Chinese()
p_04.name='黑寡妇'
p_04.speak()
输出结果
大家好,我是钢铁侠
大家好,我是蜘蛛侠
大家好,我是绿巨人
大家好,我是黑寡妇
这样来定义不同的属性不仅很麻烦,而且如果一个忘记定义,也不会提醒,就会导致执行时报错。
为此,我们可以用一种特殊的方法来解决问题。
class Chinese:
def __init__(self,name):
self.name=name #这里的self.name不一定是这样写,可以写self.abc
def speak(self):
print('大家好,我是%s'%self.name)
p_01=Chinese('钢铁侠')
p_01.speak()
p_02=Chinese('蜘蛛侠')
p_02.speak()
p_03=Chinese('绿巨人')
p_03.speak()
p_04=Chinese('黑寡妇')
p_04.speak()
这中特殊方法的好处就是,如果实例化类对象,就必须传入所需要的实参,不传入的化会有报错提醒。而且,你不需要再一个一个的添加实例的属性了,直接当成参数传入,就会再初始化方法中被self.name接收成为属性值,实例一旦创建,初始化方法立即运行,就相当于直接创建了实例对象的属性。