本篇,我们说说 Python 中的面向对象编程的基本概念。
概念
面向对象最重要的概念就是类(Class)和实例(Instance)。一个类(Class)能够创建一种新的类型(Type),其中对象(Object)就是类的实例(Instance)。可以这样来类比: 你可以拥有类型 int 的变量,也就是说存储整数的变量是 int 类的实例(对象)。
注意: Python 中,即使是整数也会被视为对象(int 类的对象),类似 C# 和 Java 中的装箱与拆箱。
对象可以使用属于它的普通变量来存储数据。这种从属于对象或类的变量叫作字段(Field)。对象还可以使用属于类的函数来实现某些功能,这种函数叫作类的方法(Method)。这两个术语很重要,它有助于我们区分函数与变量,哪些是独立的,哪些又是属于类或对象的。总之,字段与方法通称类的属性(Attribute)。
字段有两种类型 —— 它们属于某一类的各个实例或对象,或是从属于某一类本身。它们被分别称作实例变量(Instance Variables)与类变量(Class Variables)。
self
类方法与普通函数只有一种特定的区别 —— 前者必须多加一个参数在参数列表开头,但是你不用在你调用这个功能时为这个参数赋值,Python 会为它提供。这种特定的变量引用的是对象本身,按照惯例,它被赋予 self 这一名称。
类
在Python中,定义类是通过 class 关键字:
|
我们通过采用类的名称后跟一对括号的方法,给这个类创建一个对象。
方法
|
__init__ 方法
在 Python 的类中,有不少方法的名称具有着特殊的意义。__init__ 方法会在类的对象被实例化(Instantiated)时立即运行。相当于其他语言中的构造方法。
|
类变量与对象变量
字段(Field)有两种类型 —— 类变量与对象变量,它们根据究竟是类还是对象拥有这些变量来进行分类。
类变量(Class Variable)是共享的(Shared)—— 它们可以被属于该类的所有实例访问。该类变量只拥有一个副本,当任何一个对象对类变量作出改变时,发生的变动将在其它所有实例中都会得到体现。
对象变量(Object variable)由类的每一个独立的对象或实例所拥有。在这种情况下,每个对象都拥有属于它自己的字段的副本。
|
上例中,population 属于 Robot 类,因此它是一个类变量。name 变量属于一个对象(通过使用 self 分配),因此它是一个对象变量。
因此,我们通过 Robot.population 而非 self.population 引用 population 类变量。我们对于 name 对象变量采用 self.name 标记法加以称呼。
注意: 当一个对象变量与一个类变量名称相同时,类变量将会被隐藏。如果访问实例属性不存在的话,会继续访问类属性。
除了 Robot.popluation,我们还可以使用 self.__class__.population,因为每个对象都通过 self.__class__ 属性来引用它的类。
how_many 实际上是一个属于类而非属于对象的方法。这就意味着我们可以将它定义为一个 classmethod(类方法) 或是一个 staticmethod(静态方法)。我们使用装饰器(Decorator)将 how_many 方法标记为类方法。
classmethod 和 staticmethod 方法区别是 classmethod 有一个名为 cls 参数,指向该类。
可以将装饰器想象为调用一个包装器(Wrapper)函数的快捷方式,因此启用 @classmethod 装饰器等价于调用:
|
注意: Python 中所有类成员(包括数据成员)都是公开的,并且所有的方法都是虚方法(Virtual)。
继承和多态
面向对象编程的一大优点是对代码的重用(Reuse),重用的一种实现方法就是通过继承(Inheritance)机制。
新类会被称作基类(Base Class)或是超类(Superclass)。被继承的类会被称作派生类(Derived Classes)或是子类(Subclass)。
|
要想使用继承,在定义类时我们需要在类后面跟一个包含基类名称的元组。然后,我们会注意到基类的 __init__ 方法是通过 self 变量被显式调用的,因此我们可以初始化对象的基类部分。Python 不会自动调用基类的构造函数,你必须自己显式地调用它。
相反,如果我们没有在一个子类中定义一个 __init__ 方法,Python 将会自动调用基类的构造函数。
上例中,我们发现被调用的是子类型的 tell 方法,而不是 SchoolMember 的 tell 方法。理解这一问题的一种思路是 Python 总会从当前的实际类型中开始寻找方法,也就是多态。
end 参数用在超类的 tell() 方法的 print 函数中,目的是打印一行并允许下一次打印在同一行继续。这是一个让 print 能够不在打印的末尾打印出 \n (新行换行符)符号的小窍门。
获取对象信息
当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?
type()
基本类型都可以用 type() 判断:
|
如果一个变量指向函数或者类,也可以用 type() 判断:
|
type() 函数返回的是什么类型呢?它返回对应的 Class 类型:
|
isinstance()
对于继承关系来说,使用 type() 就很不方便。我们要判断 class 的类型,可以使用 isinstance() 函数。
继承关系如下:
object -> Animal -> Dog -> Husky
|
能用 type() 判断的基本类型也可以用 isinstance() 判断:
|
并且还可以判断一个变量是否是某些类型中的一种:
|
dir()
如果要获得一个对象的所有属性和方法,可以使用 dir() 函数,它返回一个包含字符串的 list。
|
类似 __xxx__ 的属性和方法在 Python 中都是有特殊用途的,比如 __len__ 方法返回长度。在 Python 中,如果你调用 len() 函数试图获取一个对象的长度,实际上,在 len() 函数内部,它自动去调用该对象的 __len__() 方法:
|
我们自己写的类,如果也想用 len(myObj) 的话,就自己写一个 __len__() 方法:
|
通过 getattr()、setattr() 以及 hasattr() 方法,我们可以直接操作一个对象的状态:
|
注意: 如果试图获取不存在的属性,会抛出 AttributeError 的错误。可以传入一个default参数,如果属性不存在,就返回默认值:
|