类定义了一种数据类型,把数据和函数绑定在一起,创建一个新的类就创建了一个新的对象类型
方法和函数的简要区分
这里讲到类的时候,对函数和方法这两个称谓做一下区分,在类中定义的函数,我们一般称为该类的方法。两者的区别不是特别大(主要是类的对象在调用方法的时候的参数self问题),刚开始学也不用特意去区分。
1. 名称和对象
对象具有个体性, 多个名称可以绑定到同一个对象中,这在其他语言中被称为别名,在涉及到可变象的时候比较有用。
2. 作用域和命名空间
2.1 作用域是名称到对象的映射,在python中通常是用字典类型来实现。命名空间的例子:内置的名称,模块中的全局名称,函数调用过程中的局部名称。在某种程度上,一个对象的属性也构成了一个命名空间。
2.2 不同命名空间之间绝对没有联系,例如,不同的模块中可能都包含max函数,但是却不会引起冲突,这是因为他们属于不同的命名空间。调用的时候用 模块名.函数名()来调用,这也是前面讲的避免名称冲突的方法
2.3 通过点(.)调用的名称称为属性,如 student.name, 其中 student 是一个对象,name是这个对象的属性。在模块中,引用名称就是属性的引用。
2.4 属性是只读或者可写的,在可写状态下,属性可以赋值,也可以删除,如:
student.name = "Bill"
del student.name
2.5 命名空间可在不同的时刻创立,并且有不同的寿命。例如:
python内置名称所在的命名空间在解释器启动时候创立,不会被删除
模块的全局命名空间在解释器读取模块的时候创立,在解释器退出的时候删除
python执行的脚本或者语句属于__main__模块,有自己的命名空间
一个函数的局部命名空间在该函数被调用的时候创立,函数返回或报错时删除(函数回归调用时的每一层有自己的一个命名空间)
2.6 作用域是指能直接触到命名空间的区域,作用域动态得发挥作用,在程序执行过程中,至少有三种嵌套作用域,他们的命名空间是可以直接接触到的:
最内层的作用域:包含局部名称,这是最先被查找的
闭合函数的作用域:包含non-local, non-global
倒数每二层的作用域:当前模块下的全局名称
最外层作用域:内置名称(built-in names)所在的作用域,最后被查找
通常,局部作用域引用当前函数的局部名称,在函数外,局部作用域和全局作用域都引用同一个命名空间,都是当前模块的命名空间。定义类的时候引入的是另一个局部作用域
定义到模块内部的函数的全局作用域就是指该模块的命名空间
如果没有 global 关键字声明,一个名称的赋值操作总是跑到最内层的作用域,赋值操作不会复制数据,只是把名称和对象(数据)绑定起来。del作用正好相反,把这种绑定解除
所有引入新名称的操作,都使用局部作用域。例如,import 模块和函数定义就会把模块和函数绑定到local作用域中
global声明的作用:声明该变量存在于全局作用域中,应该反应到全局作用域中
non-local 声明的作用: 声明该变量存在于一个闭合作用域,应该反应到这个闭合作用域中
例子:
在上面这个例子中,do_local()作用的是最内层的局部命名空间,do_nonlocal()作用在scope_test() 所在的命名空间,do_global()作用的是模块(__main__)的命名空间。
3. 定义类
3.1 关键字 class 来定义类,后面跟类名,然后在类中进行属性的和方法的定义,格式如下:
类的定义多是类中方法的定义
3.2 类的对象
类的对象支持两种操作:对象引用和实例化操作(以上例进行说明)
对象引用:testClass.name
实例化:test = testClass()
test.name
上面的实例化过程其实是默认调用了类的无参构造函数来实例化,用户也可以通过__init__定义构造函数来对类进行实例化,如:
这种情况下,实例化的时候就需要加入参数了
test2 = testClass2('bill', 25)
test2.get_Name()
test2.age
3.3 实例对象
唯一被实例对象接受的操作是属性引用,包括数据引用和方法引用,用法如上:
test2.age是数据属性的引用
test2.get_Name() 是方法属性的引用
3.4 方法对象
在上例中,test2.get_Name 就是一个方法属性的引用,返回的是一个方法对象,如果要调用这个方法,就可以用 test.get_Name() (注意,加了括号就是调用,不加就是方法对象)
既然test2.get_Name是一个方法对象,那它就可以赋值给一个变量,用这个变量来调用该方法,如:
t = test2.get_name # 赋值过程,赋值方法对象
t() #调用该方法
3.5 类变量和实例变量
简单来说,实例变量就是每个类的实例特有的变量,类变量就是类的各个实例所共享的变量,下面的这个例子很好的说明这个问题 :
在上例中,通过构造函数__init__()来定义的变量就是实例变量,因为实例是通过类的构造函数来进行实例化的,而kind是定义类的时候定义的变量,是这个类的固有变量,叫类变量,类的每个实例都会共享这个类变量
4. 继承
一个类继承另一个类,只是在类名后面多了一个要继承的类的字名,其余定义方法一样,格式如下:
新定义的类叫衍生类(子类),被继承的这个类叫基类(父类)
继承之后,子类可以直接调用父类中已经定义的方法,如果父类中方法不能满足要求时,在子类中可以重写这个方法,调用时,子类的方法要比父类的同名方法优先级要高,会优先被调用。
子类中直接调用父类的方法:父类名.方法名(self, 参数)
4.1 多态继承
一个子类同时继承多个父类,格式如下:
在多态继承中,子类调用父类方法的时候,先查找第一个父类(及其父类的父类的父类。。。)中方法,找不到的话再找第二个父类,以此类推
5. 私有实例变量
私有实例变量是不能从类的外部直接接接触的变量,只能通过调用类的方法来获取或操作私有变量,这样的好处是保护数据的私密性,但在python中,真正意义上的私有变量是不存在的
传统上,从代码格式的形式上来定义私有变量,在python中,以下划线开头的变量名被认为是私有变量,不应该从外部直接获取或修改(其实是可以的,只是这样定义)
6. 空类
类似于c语言中的struct数据类型,可以定义一个空类把命名的数据绑定在一起存储起来,如下:
7. 迭代器
使用 for 可以遍历迭代器中的每一个value, 原理如下:
(1) for 调用 iter()方法作用在一个容器对象上,返回一下迭代器对象
(2) 迭代器对象定义了__next__()方法,可以一次一个获取容器中元素
(3) 当没有元素的时候,__next__() raise StoIteration 异常
(4) for 接受到该异常信息而停止循环
例子如下:
通过定义__iter__ 和 __next__方法来自定义一个迭代器:
8. 生成器
生成器是生成迭代器的一个有效的方式,可以定义函数的形式来创建生成器,只不过把return 改为关键字 yield, 如下:
可以用生成器创建迭代器的,也都可以用基于类的方法通过实现__iter__和__next__方法来创建迭代器,但是生成器的方法会更简单一些,因为它已经自动创建了__iter__和__next__方法
9. 生成器表达式
一些简单的生成器可以用类似列表推导式的方法来创建,但是要把列表推导式的中括号换成小括号
试用情况:生成器马上应用于一个闭合函数中
特点:简洁,内存友好,不灵活
如下例:
本篇内容较多,且不容易理解,新手可以先把数据结构以及常用的内置函数,概念掌握清楚再学习这一部分会好一些,详细请参考官方文档:https://docs.python.org/3/tutorial/classes.html