Python 是一门面向对象的高级语言,正因为如此,在 Python 中创建一个类和对象是很容易的。类的继承机制允许多个基类,派生类可以覆盖基类中的任何方法,方法中可以调用基类中的同名方法。对象可以包含任意数量和类型的数据。
一、类定义
语法格式如下:
class ClassName: <statement-1> . . . <statement-N>
与函数定义 (def 语句) 一样,类定义必须先执行才能生效。把类定义放在 if 语句的分支里或函数内部试试。
二、类对象
类对象支持两种操作:属性引用和实例化。
属性引用 使用 Python 中所有属性引用所使用的标准语法: obj.name。 有效的属性名称是类对象被创建时存在于类命名空间中的所有名称。 因此,如果类定义是这样的:
class MyClass: """A simple example class""" i = 12345 def f(self): return 'hello world'
那么 MyClass.i 和 MyClass.f 就是有效的属性引用,将分别返回一个整数和一个函数对象。 类属性也可以被赋值,因此可以通过赋值来更改 MyClass.i 的值。 __doc__ 也是一个有效的属性,将返回所属类的文档字符串: “A simple example class”。
类的实例化使用函数表示法。 可以把类对象视为是返回该类的一个新实例的不带参数的函数。 举例来说(假设使用上述的类):
x = MyClass()
创建类的新 实例 并将此对象分配给局部变量 x。
实例化操作 (“调用”类对象) 会创建一个空对象。 许多类都希望创建的对象实例是根据特定初始状态定制的。 因此一个类可能会定义名为 __init__() 的特殊方法,就像这样:
def __init__(self): self.data = []
当一个类定义了 __init__() 方法时,类的实例化会自动为新创建的类实例发起调用 __init__()。 因此在这个例子中,可以通过以下语句获得一个已初始化的新实例:
x = MyClass()
当然,__init__() 方法还有一些参数用于实现更高的灵活性。 在这种情况下,提供给类实例化运算符的参数将被传递给 __init__()。 例如,
>>>class Complex: ... def __init__(self, realpart, imagpart): ... self.r = realpart ... self.i = imagpart ... >>>x = Complex(3.0, -4.5) >>>x.r, x.i (3.0, -4.5)
三、类的方法
在类的内部,使用 def 关键字可以为类定义一个方法,与一般函数定义不同,类方法必须包含参数 self,且为第一个参数:
#!/usr/bin/python3 #类定义 class people: #定义基本属性 name = '' age = 0 #定义私有属性,私有属性在类外部无法直接进行访问 __weight = 0 #定义构造方法 def __init__(self,n,a,w): self.name = n self.age = a self.__weight = w def speak(self): print("%s 说: 我 %d 岁。" %(self.name,self.age)) # 实例化类 p = people('ABC',10,30) p.speak()
执行以上程序输出结果为:
ABC 说: 我 10 岁。
四、继承
Python 同样支持类的继承,如果一种语言不支持继承,类就没有什么意义。派生类的定义如下所示:
class DerivedClassName(BaseClassName1): <statement-1> . . . <statement-N>
名称 BaseClassName 必须定义于可从包含所派生的类的定义的作用域访问的命名空间中。 作为基类名称的替代,也允许使用其他任意表达式。 例如,当基类定义在另一个模块中时,这就会很有用处:
class DerivedClassName(modname.BaseClassName):
派生类定义的执行过程与基类相同。派生类的实例化没有任何特殊之处: DerivedClassName() 会创建该类的一个新实例。 方法引用将按以下方式解析:搜索相应的类属性,如有必要将按基类继承链逐步向下查找,如果产生了一个函数对象则方法引用就生效。
Python有两个内置函数可被用于继承机制:
- 使用 isinstance() 来检查一个实例的类型: isinstance(obj, int) 仅会在 obj.__class__ 为 int 或某个派生自 int 的类时为 True;
- 使用 issubclass() 来检查类的继承关系: issubclass(bool, int) 为 True,因为 bool 是 int 的子类。 但是,issubclass(float, int) 为 False,因为 float 不是 int 的子类。
五、多重继承
Python 也支持一种多重继承。 带有多个基类的类定义语句如下所示:
class DerivedClassName(Base1, Base2, Base3): <statement-1> . . . <statement-N>
需要注意圆括号中父类的顺序,若是父类中有相同的方法名,而在子类使用时未指定,Python 从左至右搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
对于多数目的来说,在最简单的情况下,你可以认为搜索从父类所继承属性的操作是深度优先、从左到右的,当层次结构存在重叠时不会在同一个类中搜索两次。 因此,如果某个属性在DerivedClassName 中找不到,就会在 Base1 中搜索它,然后(递归地)在 Base1 的基类中搜索,如果在那里也找不到,就将在 Base2 中搜索,依此类推。
六、私有变量
那种仅限从一个对象内部访问的“私有”实例变量在 Python 中并不存在。 但是,大多数 Python 代码都遵循这样一个约定:带有一个下划线的名称 (例如 _spam) 应该被当作是 API 的非公有部分 (无论它是函数、方法或是数据成员)。 这应当被视为一个实现细节,可能不经通知即加以改变。
由于存在对于类私有成员的有效使用场景(例如避免名称与子类所定义的名称相冲突),因此存在对此种机制的有限支持,称为 名称改写。 任何形式为 __spam 的标识符(至少带有两个前缀下划线,至多一个后缀下划线)的文本将被替换为 _classname__spam,其中 classname 为去除了前缀下划线的当前类名称。 这种改写不考虑标识符的句法位置,只要它出现在类定义内部就会进行。
名称改写有助于让子类重载方法而不破坏类内方法调用。例如:
class Mapping: def __init__(self, iterable): self.items_list = [] self.__update(iterable) def update(self, iterable): for item in iterable: self.items_list.append(item) __update = update # private copy of original update() method class MappingSubclass(Mapping): def update(self, keys, values): # provides new signature for update() # but does not break __init__() for item in zip(keys, values): self.items_list.append(item)
上面的示例即使在 MappingSubclass 引入了一个 __update 标识符的情况下也不会出错,因为它会在 Mapping 类中被替换为 _Mapping__update 而在 MappingSubclass 类中被替换为 _MappingSubclass__update。
请注意,改写规则的设计主要是为了避免意外冲突;访问或修改被视为私有的变量仍然是可能的。这在特殊情况下甚至会很有用,例如在调试器中。
请注意传递给 exec() 或 eval() 的代码不会将发起调用类的类名视作当前类;这类似于 global 语句的效果,因此这种效果仅限于同时经过字节码编译的代码。 同样的限制也适用于 getattr(), setattr() 和 delattr(),以及对于 __dict__ 的直接引用。