这一篇教程开始,我们一起了解Python中的类(Class)。
一、类相关的概念
我们先来接触一些概念。
1、类的概念。
类是根据事物本身的性质或特点(简称特性)而分成的门类。(来自百度百科)
例如,我们通常说人类、鸟类、家具类、电器类、文具类……
2、特征的概念。
特征是一个客体或一组客体特性的抽象结果。(来自百度百科)
通俗一些来说,特征是一个或多个事物所具有的相同或相像特性的总和。
3、抽象的概念
抽象简单来说就是抽取相像的部分,所以对特性的抽象,就是把所有相像或相同的特性进行抽取。
4、特性的概念
参考类的概念,特性(Attribute)就是事物本身的性质或特点。
例如,人类有名字,鸟类会飞,家具可以用来存储,电器需要电源,文具用来学习等等,都是事物的性质或特点。
注意,特性不仅仅包含属性(例如名称、颜色等内在的状态)还包含了方法(例如飞行,说话等外在的行为),理解这个很重要。
5、对象的概念
对象(Object)就是客体,客观存在的具体的事物。
所以说“万物皆对象”。(这句话在Python中尤其正确)
那么,类的实例(Instance)显然就是对象。
例如,人类的一个实例就是一个具体的人,这个具体的人就是对象。
通过类创建实例对象的操作,我们称之为实例化。
实例对象包含了类的所有特性。
————————————————————————————
通过上面这些概念的理解,我们回过头来重新理解类的概念。
类是某一种对象的总称,它包含从某一种对象中抽取出来的相同或相像的全部特性。
相信现在上面这句话理解起来不会再困难,而对理解接下来的内容也很有帮助。
二、定义类
定义类使用class关键字,类的名称一般会使用首字母大写的单词。
格式:class 类名称(超类名称):
超类是指当前的类所继承的类,如果没有继承超类括号部分可以省略。(继承的概念在后面的内容中有详细的介绍)
示例代码:
class Human: # 定义类(人类) def set_name(self, user_name): # 类的函数 self.name = user_name # 修改类的变量 def get_name(self): # 类的函数 return self.name # 返回类的变量 def say_hello(self): # 类的函数 print('嗨!大家好,我的是%s!' % self.name) # 使用类的特性
上方代码就是类的创建,注意里面的变量和函数都是类的特性。例如:name是姓名,say_hello是打招呼,这些都是人类通常情况下共有的特性。
另外,值得注意的是,类的定义实际上就是执行代码块,而不仅仅是在类的里面定义方法。
示例代码:
class Class: # 创建类 print('你好,我是类中的语句,我能直接被执行!')
运行一下代码,我们能够看到类中的print语句直接被执行了。
而如果仅仅是一个函数的话,即便里面包含同样的语句,运行代码时,print语句是不会被执行的。
提示:回想一下我们以前写过的自定义函数,都需要编写语句调用才能够执行。
三、封装(Encapsulation)
将抽象得到的全部特性结合为一个整体(也可以看做将数据和操作捆绑到一起),形成类即为封装。
封装后,所有特性都是类的成员。
除此之外,封装还是达到接口与实现分离的过程。
封装,即隐藏对象的特性和实现细节(实现),仅对外提供可调用的特性(接口),将访问控制在只能够对被允许的对特性进行读与修改的级别。
从这个方面来看,封装就像组装的电视机,使用它的人无需知道电视机由哪些元器件和运行程序(特性)组成,也不需要知道它的具体工作过程(方法的实现细节),只需要知道能够进行的开、关以及换台(允许调用的方法)等操作就可以了。
那么,封装有什么样的好处呢?
我们通过一些示例来了解。
示例代码:(未封装)
global_name = '' # 创建全局变量并赋值 class Human(object): # 创建类(人类) def set_name(self, name): # 定义方法修改全局变量的值 global global_name # 声明引用全局变量 global_name = name # 全局变量重新绑定值 def get_name(self): # 定义方法获取全局变量值 return global_name def say_hello(self): # 类的方法 print('嗨!大家好,我是%s!' % global_name) # 使用类的特性
上面的代码中,我们把特性name(类里面的变量)改为为全局变量。
接下来,我们通过Human类(人类)实例化出一个实例对象person(人)。
然后,为这个实例对象(人)设置姓名。
并且,调用实例对象中(人)的say_hello()方法,让这个实例对象(人)和我们打个招呼!
示例代码:(类的实例化和实例方法的调用)
person1 = Human() # 类的实例化 person1.set_name('小楼') person1.say_hello() # 调用实例对象的方法,显示输出结果为:嗨!大家好,我是小楼!
看上去没有问题。
接下来,我们通过类实例化出第2个实例对象(人),然后试试给新的实例对象(人)也取个名字,并且让新的实例对象(人)也打个招呼。
示例代码:(错误示例)
person1 = Human() # 类的实例化 person1.set_name('小楼') person2 = Human() # 类的实例化 person2.set_name('樱井') person1.say_hello() # 显示输出结果为:嗨!大家好,我是樱井! person2.say_hello() # 显示输出结果为:嗨!大家好,我是樱井!
现在大家能够看到问题了。
虽然是两个实例对象,并且调用每个实例对象自己的方法取名字,但是他们的set_name()方法都修改同一个全局变量,这样就会互相干扰。
而将全局变量变为封装到类里面的特性,就不会有这样的问题。
封装后的代码,就是上方我们创建的第一个类的代码。
我们同样对这个类进行两次实例化,并且为两个实例对象设置姓名,再调用每个实例对象打招呼的方法。
示例代码:(封装后)
person1 = Human() # 类的实例化 person1.set_name('小楼') person2 = Human() # 类的实例化 person2.set_name('樱井') person1.say_hello() # 显示输出结果为:嗨!大家好,我是小楼! person2.say_hello() # 显示输出结果为:嗨!大家好,我是樱井!
这一次就没有任何问题了。
最后,我们再来了解一块内容,就是函数与方法的区别。
可能看到这里有些人会有点儿懵:函数和方法不是同一个东西吗?
在之前的教程中,为了便于理解函数,我们没有严格区分函数与方法的概念。
实际上,函数和方法是有区别的。
这里借用官方文档的示例与说明。
示例代码:
class MyClass: """A simple example class(一个简单的示例类)""" i = 12345 def f(self): return 'hello world' x = MyClass()
说明原文:
Valid method names of an instance object depend on its class. By definition, all attributes of a class that are function objects define corresponding methods of its instances. So in our example, x.f is a valid method reference, since MyClass.f is a function, but x.i is not, since MyClass.i is not. But x.f is not the same thing as MyClass.f — it is a method object, not a function object.
说明大意:
实例对象有效的方法名取决于类。根据定义,类的所有特性中,函数对象都是在定义其实例中相应的方法。所以,在我们的例子中,x.f将是一个有效的方法调用,因为MyClass.f是一个函数。但是x.i不是有效的方法调用,因为MyClass.i不是一个函数。但是,x.f和MyClass.f不同,x.f是一个方法对象,而不是一个函数对象。
我们通过print函数来查看一下,是不是这样?
示例代码:
print(x.f) # 显示输出结果为:<bound method MyClass.f of <__main__.MyClass object at 0x000000000265D630>> print(MyClass.f) # 显示输出结果为:<function MyClass.f at 0x000000000265FA60>
大家能够看到x.f是MyClass实例对象的一个“bound method”(绑定方法),由MyClass.f函数定义。
而MyClass.f是一个“function”(函数)。
那么,我们可以这样理解:绑定到实例对象特性上的函数就是方法。
另外,还有一个区别在于类的函数与实例方法的第一个参数是“self”。
参数“self”绑定到了方法所属的实例,通过参数“self”我们能够访问实例对象的特性。
也正是因为如此,通过实例调用方法时,这个参数不需要提供。
而通过类调用函数的时候,参数“self”需要提供,如果访问类的特性填入类名称,如果访问实例特性填入实例名称。
例如,我们借用封装好的Human类,继续写入一些代码。
示例代码:(访问类的特性)
Human.set_name(Human,'明步') # 设置类的特性 Human.say_hello(Human) # 显示输出结果为:嗨!大家好,我是明步!
如果调用时,我们不输入Human这个参数,则会抛出异常。
例如:set_name()未输入参数Human,则会抛出异常。
TypeError: set_name() missing 1 required positional argument: ‘user_name’
类型错误:set_name() 函数丢失了1个必须的位置参数: ‘user_name’
也就是说,程序把输入的参数字符串当成了传入的self参数,然后异常提示还需要传入第2个user_name参数。
四、私有化(Private)
虽然,封装是为了避免让使用者看到多余的信息或是对类的特性直接进行控制,但是,在Python中这好像没有什么用。
我们看下面这段代码:
示例代码:
person = Human() # 类的实例化 person.name = '小楼' # 修改类的特性 print(person.get_name()) # 显示输出结果为:小楼
很显然,我们还是能够对对象的特性直接进行修改。
下面有一种解决方案,就是给特性名称前面加上双下划线“__”(包含方法名称),但是也只是“自欺欺人”。
示例代码:
class Human: # 创建类(人类) def set_name(self, user_name): # 类的方法 self.__name = user_name # 修改类的特性 def get_name(self): # 类的方法 return self.__name # 返回类的特性 def say_hello(self): # 类的方法 print('嗨!大家好,我的是%s!' % self.__name) # 使用类的特性 person = Human() # 类的实例化 person.__name = '小楼' # 抛出异常
运行代码,这时会抛出异常。
AttributeError: ‘Human’ object has no attribute ‘_Human__name’
属性异常:’Human’ 对象不包含’_Human__name’特性。
真的不包含吗?然并卵!
实际上,这个异常提示暴露了特性信息,名称前方带有双下划线的特性(包含方法)可以通过“单下划线+类名称+双下划线+特性名称”进行控制,就像异常中的“_Human__name”。
示例代码:
person = Human() # 类的实例化 person._Human__name = '小楼' # 修改对象的特性 print(person.get_name()) # 显示输出结果为:小楼
运行代码,我们能够看到类的特性依然被修改了。
所以,不想他人直接访问对象的特性(包含方法)是不可能的,只是带有双下划线的名称带有很明显的警示作用,就像提示前面的红色叹号一样。
本节知识点:
1、类的概念
2、创建类
3、封装
4、特性与方法私有化
本节英文单词与中文释义:
1、class:类
2、instance:实例
3、method:方法
4、attribute:特性
5、self:自己
6、encapsulation:封装
7、private:私有
8、human:人类
9、person:人
10、say:说
11、hello:你好
转载请注明:魔力Python » Python3萌新入门笔记(26)