对象可以使用属于这个对象的变量存储数据。属于对象或类的变量称作字段(fields)。
对象同样可以利用属于类的函数实现所需的功能。这些函数被称作类的方法(method)。
字段与方法统称为类属性。
字段分为两种类型 – 它们既可以属于类的实例/对象也可以属于类本身,两者分别称为实例变量与类变量。
一个类通过关键字class
创建。字段与方法被列在类的缩进块里。
类方法与普通函数只有一个特殊区别 – 类方法必须增加一个额外的形参,而且它必须处于第一个形参的位置,但是在调用类方法时不要为这个额外的形参传值,python会自动代劳。这个特别的变量引用对象本身,按照惯例它被命名为self
。
尽管你可以随便为这个形参取名字,但我强烈建议你使用self – 其它名字会让人皱眉头的。使用标准名字是有很多好处的 – 任何你的代码的读者都会立即明白它代表什么,甚至当你使用标准名字时专业的IDE都会更好的帮助你。
Python中的self
相当于C++中的this指针和java/C#中的this引用。
你一定感到疑惑Python是如何为self赋值的,为什么你无需亲力亲为呢?举个例子可以让事情变的明朗。
假设你有一个叫做MyClass的类与一个它的实例myobject。 当你调用这个对象的方法时myobject.method(arg1, arg2),
python会自动将其转换为myobject.method(myobject, arg1, arg2) – 这就是关于self的内幕。
这也意味着如果你有一个无需参数的方法,你也仍然需要一个self实参。
下面的例子是一个最简单的类了。
1 | #!/usr/bin/python |
1 | $ python simplestclass.py |
我们已经讨论过对象/类可以拥有类似函数一样的方法,只不过这些函数必须加上额外的self
变量。现在我们就来看个例子。
1 | #!/usr/bin/python |
1 | $ python method.py |
本例中我们使用了self,注意方法sayHi虽无需参数但在函数中仍然要写上self。
__init__
方法__init__
方法在类对象被实例化时立即执行。此方法专用于初始化对象。另外注意方法名中的双下划线前后缀。
1 | #!/usr/bin/python |
1 | $ python class_init.py |
这里我们定义一个带有名为name形参的__init__方法(除了寻常的self)。初始化之后,我们就可以在方法中使用self.name字段了,方法sayHi演示了这点。
数据即字段,只不过是绑定在类和对象的名字空间中的普通变量。这意味着这些名字只在其所属类和对象的上下文中合法有效。这就是它们被称作名字空间的原因。
有两种字段类型 —— 类变量和对象变量,它们根据所有者是类还是对象而区分开来。
类对象是共享的 —— 它们可以被一个类的所有实例存取。即一个类对象在类中只存在一个拷贝,任何对象对其的修改都会反映到所有对象上。
而每个独立的类对象/实例都拥有自己的对象变量。这样每个对象都有属于自己的字段拷贝即这些字段非共享,不同对象中的同名对象变量彼此没有任何关联。
1 | #!/usr/bin/python |
1 | (Initializing R2-D2) |
howMany实际上是一个属于类的方法而不是对象。这意味着我们可以将及其定义为classmethod也可以定义为staticmethod,这取决于我们是否需要知道我们是哪个类的一部分。因为我们无需类似信息,所以我们使用staticmethod。
另外我们还可以通过装饰符(decorators)达到同样的效果:
1 |
|
装饰符可以被想象成调用一条显式语句的捷径,就像在这个例中看到的一样。
本例中,我们还在类与方法中使用了文档字符串。我们可以在运行时使用Robot.__doc__
和Robot.sayhi.__doc__
分别访问类和方法的文档字符串。
就像__init__
方法,这里还有另一个特殊方法__del__
,当对象被删除的时候将被调用。对象被删除是指对象不再被使用了,它占用的空间将返回给系统以便重复使用。在__del__
中我们只是简单的将Robot.population减1。
当对象不再被使用时__del__
方法将可以被执行,但无法保证到底啥时执行它。如果你想显式执行它则必须使用del
语句,就象本例中做的那样。(注:本例中del
后对象的引用计数降为0)。
python中所有类成员(包括数据成员)全部为public,并且所有方法都为virtual。
只有一个例外:如果你使用的数据成员,其名字带有双下划线前缀例如__privatevar,则python将使用名字混淆机制有效的将其作为private变量。
另外存在一个惯例,任何只在类或对象内部使用的变量应该以单下划线为前缀,其他的变量则为public可以被其它类/变量使用。
记住这只是一个惯例而不是强迫(不过双下划线前缀例外).
有一些诸如__init__
和__del__
的方法在类中拥有特殊的含义。特殊方法用于模拟某些内建类型的行为。
例如,你希望为你的类使用x[key]
索引操作(就像在列表和元组中那样),那么你仅仅需要实现__getitem__
方法就可以了。
顺便思考一下,python正是这样实现list类的!
一些有用的特殊方法列在下表中。如果你想了解所有的特殊方法,详见 Special method names 。
方法名 | 解释 |
---|---|
__init__(self, ...) |
在对象被返回以变的可用前调用 |
__del__(self) |
在对象被销毁前调用 |
__str__(self) |
在使用print函数或str()时调用 |
__lt__(self, other) |
在使用小于运算符时(<)调用。类似的其它运算符也存在对象的特殊方法(+, >等) |
__getitem__(self, key) |
当使用x[key]索引操作时调用 |
__len__(self) |
当使用内建len()函数时调用。 |
继承可以细化为 继承(泛化) 和 组合(聚合) 两种,如下面这张图所示。
1 | #!/usr/bin/python |
1 | $ python inherit.py |
为了使用继承,我们在类定义的类名后的一个元组中指定基类名。然后我们注意到使用super函数显式调用了基类的__init__方法,这样我们就能初始化对象的基类部分了。牢记这点非常重要 – Python不会自动调用基类的构造函数,你必须自己显式的调用它。
同时应该注意到程序调用的是子类型的方法而不是基类的方法。理解这个机制的一个思路是python永远先在实际类型中查找被调用的方法,这个例子中就是如此。如果Python没有找到被调用方法,则会在基类中逐个查找,查找顺序由类定义时在元组中指定的基类顺序决定。
在上面的例子中,显示调用基类的函数是直接通过使用基类名字来完成的。例如:
1 | parent.altered() |
但这种做法有一个潜在性的隐患:如果以后更换了基类,则这里的名字就要更换。如果重载的函数很多,修改起来就很麻烦。使用super函数可以解决这个问题:
1 | super(Child, self).altered() |
super函数传入的参数是当前子类的类名, python会为其找到相应的基类,然后调用相应方法。
从 Python3 开始,super函数被进一步简化了,可以不再给super传参数:
1 | super().altered() |
还有另外一种非常有用的方法可以达到和上面完全一样的目的,它可以允许一个类直接使用和封装来自其他类和模块中定义的接口,而不需要依靠继承。
1 | #!/usr/bin/python |
1 | $ python composition.py |
三条指导准则:
迭代器就是一个定义了 __iter()__
方法的类。例如这是一个生成Fibonacci序列的迭代器:
1 | class Fib: |
__iter__()
方法用于初始化迭代器,每个__iter__()
方法都需要做的就是必须返回一个迭代器。iter(fib)
的时候,__iter__()
就会被调用。(正如你等下会看到的, for 循环会自动调用它 , 你也可以自己手动调用。) 在完成迭代器初始化后,(在本例中, 重置我们两个计数器 self.a
和 self.b
), __iter__()
方法能返回任何实现了 __next__()
方法的对象。 在本例(甚至大多数例子)中, __iter__()
仅简单返回 self, 因为该类实现了自己的 __next__()
方法。next()
方法时,__next__()
会自动调用。 随后会有更多理解。__next__()
方法抛出 StopIteration
异常, 这是给调用者表示迭代用完了的信号。 和大多数异常不同, 这不是错误;它是正常情况,仅表示迭代器没有值可产生了。 如果调用者是 for 循环, 它会注意到该 StopIteration
异常并优雅的退出。 (换句话说,它会吞掉该异常。) 这点神奇之处就是使用 for 的关键。1 | from fibonacci2 import Fib |
for 循环内有魔力。下面是究竟发生了什么:
Fib(1000)
。 这返回 Fib 类的实例。 叫它 fib_inst
。iter(fib_inst)
, 它返回迭代器。叫它 fib_iter
。 本例中, fib_iter == fib_inst
, 因为 __iter__()
方法返回 self,但 for 循环不知道(也不关心)那些。next(fib_iter)
, 它又调用 fib_iter
对象的 __next__()
方法,产生下一个菲波拉稀计算并返回值。 for 拿到该值并赋给 n, 然后执行n值的 for 循环体。next(fib_iter)
抛出 StopIteration
异常时, for循环将吞下该异常并优雅退出。 (其他异常将传过并如常抛出。)