类
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() # 显示输出结果为:嗨!大家好,我是李小狼!
四、私有化(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()) # 显示输出结果为:小楼
运行代码,我们能够看到类的特性依然被修改了。
所以,不想他人直接访问对象的特性(包含方法)是不可能的,只是带有双下划线的名称带有很明显的警示作用,就像提示前面的红色叹号一样。