天天看点

类与对象

类的定义非常巧妙的运用了命名空间,要完全理解接下来的知识,需要先理解作用域和命名空间的工作原理。另外,这一切的知识对于任何高级 python 程序员都非常有用。

命名空间 是从命名到对象的映射。

当前命名空间主要是通过 python 字典实现的,不过通常不关心具体的实现方式(除非出于性能考虑),以后也有可能会改变其实现方式。

以下有一些命名空间的例子:内置命名(像 abs() 这样的函数,以及内置异常名)集,模块中的全局命名,函数调用中的局部命名。某种意义上讲对象的属性集也是一个命名空间。关于命名空间需要了解的一件很重要的事就是不同命名空间中的命名没有任何联系,例如两个不同的模块可能都会定义一个名为 maximize 的函数而不会发生混淆-用户必须以模块名为前缀来引用它们。

称 python 中任何一个“<code>.</code>”之后的命名为 属性

例如,表达式 <code>z.real</code> 中的 <code>real</code> 是对象 <code>z</code> 的一个属性。

严格来讲,从模块中引用命名是引用属性:

表达式 <code>modname.funcname</code> 中,<code>modname</code> 是一个模块对象,<code>funcname</code> 是它的一个属性。

因此,模块的属性和模块中的全局命名有直接的映射关系:它们共享同一命名空间!

属性可以是只读过或写的。后一种情况下,可以对属性赋值。你可以这样: <code>modname.the_answer = 42</code>。可写的属性也可以用 <code>del 语句</code>删除。

例如: <code>del modname.the_answer</code> 会从 <code>modname 对象</code>中删除 <code>the_answer 属性</code>。

不同的命名空间在不同的时刻创建,有不同的生存期。包含内置命名的命名空间在 python 解释器启动时创建,会一直保留,不被删除。模块的全局命名空间在模块定义被读入时创建,通常,模块命名空间也会一直保存到解释器退出。由解释器在最高层调用执行的语句,不管它是从脚本文件中读入还是来自交互式输入,都是 <code>__main__</code> 模块的一部分,所以它们也拥有自己的命名空间(内置命名也同样被包含在一个模块中,它被称作 <code>builtins</code> )。

当调用函数时,就会为它创建一个<code>局部命名空间</code>,并且在函数返回或抛出一个并没有在函数内部处理的异常时被删除。(实际上,用遗忘来形容到底发生了什么更为贴切。)当然,每个递归调用都有自己的局部命名空间。

<code>作用域</code> 就是一个 python 程序可以直接访问命名空间的正文区域。这里的直接访问意思是一个对名称的错误引用会尝试在命名空间内查找。尽管作用域是静态定义,在使用时他们都是动态的。每次执行时,至少有三个命名空间可以直接访问的作用域嵌套在一起:

包含局部命名的使用域在最里面,首先被搜索;其次搜索的是中层的作用域,这里包含了同级的函数;

最后搜索最外面的作用域,它包含内置命名。

首先搜索最内层的作用域,它包含局部命名任意函数包含的作用域,是内层嵌套作用域搜索起点,包含非局部,但是也非全局的命名

接下来的作用域包含当前模块的全局命名

最外层的作用域(最后搜索)是包含内置命名的命名空间

以下是一个示例,演示了如何引用不同作用域和命名空间,以及 global 和 nonlocal 如何影响变量绑定:

local 赋值语句是无法改变 scope_test 的 spam 绑定。nonlocal 赋值语句改变了 scope_test 的 spam 绑定,并且 global 赋值语句从模块级改变了 spam 绑定。

名词

描述

类(class)

用来描述具有相同的属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。对象是类的实例。

类变量

类变量在整个实例化的对象中是公用的。类变量定义在类中且在函数体之外。类变量通常不作为实例变量使用。

数据成员

类变量或者实例变量用于处理类及其实例对象的相关的数据。

方法重写

如果从父类继承的方法不能满足子类的需求,可以对其进行改写,这个过程叫方法的覆盖(override),也称为方法的重写。

实例变量

定义在方法中的变量,只作用于当前实例的类。

继承

即一个派生类(derived class)继承基类(base class)的字段和方法。继承也允许把一个派生类的对象作为一个基类对象对待。例如,有这样一个设计:一个dog类型的对象派生自animal类,这是模拟"是一个(is-a)"关系(例图,dog是一个animal)。

实例化

创建一个类的实例,类的具体对象。

方法

类中定义的函数。

对象

通过类定义的数据结构实例。对象包括两个数据成员(类变量和实例变量)和方法(实例方法,类方法,静态方法)

空行

函数之间或类的方法之间用空行分隔,表示一段新的代码的开始。类和函数入口之间也用一行空行分隔,以突出函数入口的开始。

空行与代码缩进不同,空行并不是python语法的一部分。书写时不插入空行,python解释器运行也不会出错。但是空行的作用在于分隔两段不同功能或含义的代码,便于日后代码的维护或重构。

记住:空行也是程序代码的一部分。

类的格式:

数据属性会覆盖同名的方法属性。为了避免意外的名称冲突,这在大型程序中是极难发现的 bug,使用一些约定来减少冲突的机会是明智的。

可能的约定包括:大写方法名称的首字母,使用一个唯一的小字符串(也许只是一个下划线)作为数据属性名称的前缀,或者方法使用动词而数据属性使用名词。

数据属性可以被方法引用,也可以由一个对象的普通用户(客户)使用。换句话说,类不能用来实现纯净的数据类型。

事实上,python 中不可能强制隐藏数据——一切基于约定(如果需要,使用 c 编写的 python 实现可以完全隐藏实现细节并控制对象的访问。这可以用来通过 c 语言扩展 python)。

客户应该谨慎的使用数据属性——客户可能通过践踏他们的数据属性而使那些由方法维护的常量变得混乱。

注意:只要能避免冲突,客户可以向一个实例对象添加他们自己的数据属性,而不会影响方法的正确性——再次强调,命名约定可以避免很多麻烦。

从方法内部引用数据属性(或其他方法)并没有快捷方式。这实际上增加了方法的可读性:当浏览一个方法时,在局部变量和实例变量之间不会出现令人费解的情况。

一般,方法的第一个参数被命名为 self。

这仅仅是一个约定:对 python 而言,名称 <code>self</code> 绝对没有任何特殊含义。(但是请注意:如果不遵循这个约定,对其他的 python 程序员而言你的代码可读性就会变差,而且有些 类查看器 程序也可能是遵循此约定编写的。)

类属性的任何函数对象都为那个类的实例定义了一个方法。函数定义代码不一定非得定义在类中:也可以将一个函数对象赋值给类中的一个局部变量。

例如:

类可以看作是一种把对象分组归类的方法。

旧式类的定义(即将被淘汰)

新式类与旧式类的区别主要表现在对超类命名空间检索的使用算法不同。

旧式类可以直接省略括号和超类列表,而新式类以object作为python中所有类的默认超类。

类对象支持两种操作:属性引用和实例化。

属性引用使用和 python 中所有的属性引用一样的标准语法:<code>obj.name</code>。

类对象创建后,类命名空间中所有的命名都是有效属性名。

示例:

类实例化后,可以使用其属性,实际上,创建一个类之后,可以通过类名访问其属性。

很多类都倾向于将对象创建为有初始状态的。因此类可能会定义一个名为 <code>__init__()</code> 的特殊方法(构造方法),像下面这样:

类定义了 <code>__init__()</code> 方法的话,类的实例化操作会自动调用 <code>__init__()</code> 方法。所以在下例中,可以这样创建一个新的实例:

当然,<code>__init__()</code> 方法可以有参数,参数通过 <code>__init__()</code> 传递到类的实例化操作上。例如:

在类地内部,使用 <code>def</code> 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self, 且为第一个参数,self 代表的是类的实例。

类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的第一个参数名称, 按照惯例它的名称是self。

从执行结果可以很明显的看出,self 代表的是类的实例,代表当前对象的地址,而 self.class 则指向类。

self 不是 python 关键字,我们把他换成 runoob 也是可以正常执行的:

# 新式类

类的成员:

成员变量:

成员方法:

实例方法

类方法

静态方法

property(修饰器)

以下均以例子说明:

python的私有并不是绝对的。

python解释器对于类中所有加了双下划线的成员名会做一些名字上的替换,若存在一个成员的名字为<code>__fooname</code>,则解释器会在检测到它存在后将其替换为<code>_classname__fooname</code>,即在成员名前加上单下划线开头的类名。

类的私有属性

<code>__private_attrs</code>:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。在类内部的方法中使用时 <code>self.__private_attrs</code>。

类的方法

在类地内部,使用 def 关键字来定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数,self 代表的是类的实例。

self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定是用 self。

类的私有方法

<code>__private_method</code>:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类地外部调用。<code>self.__private_methods</code>。

类的专有方法:

<code>__init__</code> : 构造函数,在生成对象时调用

<code>__del__</code>: 析构函数,释放对象时使用

<code>__repr__</code> : 打印,转换

<code>__setitem__</code>: 按照索引赋值

<code>__getitem__</code>: 按照索引获取值

<code>__len__</code>: 获得长度

<code>__cmp__</code>: 比较运算

<code>__call__</code>: 函数调用

<code>__add__</code>: 加运算

<code>__sub__</code>: 减运算

<code>__mul__</code>: 乘运算

<code>__div__</code>: 除运算

<code>__mod__</code>: 求余运算

<code>__pow__</code>: 乘方

类变量和类方法可以被实例成员方法以只读的方式访问。

静态方法位于类命名空间中的一种特殊函数,无法对任何实例成员进行操作。

可以访问类变量和类方法,但是,类的实例对象无法调用静态方法。

目的:

修饰实例方法,以改变对外的访问方式,使得其在外部被访问时更像是在访问一个变量,而非函数。

从上述结果可以看出,bird类的名叫<code>name</code>的对象完全成为了<code>__name</code>的替身。有了<code>name</code>作为访问的中间层,一些验证的逻辑就可以在<code>name</code>的方法中实现了。

<code>__init__</code>关键字是类的构造方法。

在python中,为一个类创建一个 实例对象 时,会立即调用类的构造方法(即当对象被创建后,python解释器会去类中查找<code>__init__</code>函数)。

<code>__init__</code>函数为类添加一些初始化的属性或功能。

类的继承:

函数重写

多重继承

python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。派生类的定义如下所示:

需要注意圆括号中基类的顺序,若是基类中有相同的方法名,而在子类使用时未指定,python从左至右搜索 即方法在子类中未找到时,从左到右查找基类中是否包含方法。

baseclassname(示例中的基类名)必须与派生类定义在一个作用域内。除了类,还可以用表达式,基类定义在另一个模块中时这一点非常有用:

<code>class derivedclassname(modname.baseclassname):</code>

如果你的父类方法的功能不能满足你的需求,你可以在子类重写你父类的方法,实例如下:

多态

动态类型:一个引用可以绑定任意类型的对象。

python同样支持运算符重载,我么可以对类的专有方法进行重载,实例如下:

探寻有趣之事!