对象可以使用属于这个对象的变量存储数据。属于对象或类的变量称作字段(fields)

对象同样可以利用属于类的函数实现所需的功能。这些函数被称作类的方法(method)

字段与方法统称为类属性

字段分为两种类型 – 它们既可以属于类的实例/对象也可以属于类本身,两者分别称为实例变量类变量

一个类通过关键字class创建。字段与方法被列在类的缩进块里。

self

类方法与普通函数只有一个特殊区别 – 类方法必须增加一个额外的形参,而且它必须处于第一个形参的位置,但是在调用类方法时不要为这个额外的形参传值,python会自动代劳。这个特别的变量引用对象本身,按照惯例它被命名为self

尽管你可以随便为这个形参取名字,但我强烈建议你使用self – 其它名字会让人皱眉头的。使用标准名字是有很多好处的 – 任何你的代码的读者都会立即明白它代表什么,甚至当你使用标准名字时专业的IDE都会更好的帮助你。

写给C++/Java/C#程序员

Python中的self相当于C++中的this指针和java/C#中的this引用。

Python如何为self赋值

你一定感到疑惑Python是如何为self赋值的,为什么你无需亲力亲为呢?举个例子可以让事情变的明朗。

假设你有一个叫做MyClass的类与一个它的实例myobject。 当你调用这个对象的方法时myobject.method(arg1, arg2),

python会自动将其转换为myobject.method(myobject, arg1, arg2) – 这就是关于self的内幕。

这也意味着如果你有一个无需参数的方法,你也仍然需要一个self实参。

示例

下面的例子是一个最简单的类了。

1
2
3
4
5
6
7
8
#!/usr/bin/python
# Filename: simplestclass.py

class Person:
pass # 空语句块

p = Person()
print(p)

输出

1
2
$ python simplestclass.py
<__main__.Person object at 0x019F85F0>

对象方法

我们已经讨论过对象/类可以拥有类似函数一样的方法,只不过这些函数必须加上额外的self变量。现在我们就来看个例子。

示例

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/python
# Filename: method.py

class Person:
def sayHi(self):
print('Hello, how are you?')

p = Person()
p.sayHi()
# 在这个简短的例子中同样可以写成Person().sayHi()

输出

1
2
$ python method.py
Hello, how are you?

说明

本例中我们使用了self,注意方法sayHi虽无需参数但在函数中仍然要写上self

__init__方法

__init__方法在类对象被实例化时立即执行。此方法专用于初始化对象。另外注意方法名中的双下划线前后缀。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/python
# Filename: class_init.py

class Person:
def __init__(self, name):
self.name = name

def sayHi(self):
print('Hello, my name is', self.name)

p = Person('Swaroop')
p.sayHi()
# 在这个简短的例子中同样可以写成Person().sayHi()

输出

1
2
$ python class_init.py
Hello, my name is Swaroop

说明

这里我们定义一个带有名为name形参的__init__方法(除了寻常的self)。初始化之后,我们就可以在方法中使用self.name字段了,方法sayHi演示了这点。

类和对象变量

数据即字段,只不过是绑定在类和对象的名字空间中的普通变量。这意味着这些名字只在其所属类和对象的上下文中合法有效。这就是它们被称作名字空间的原因。

有两种字段类型 —— 类变量和对象变量,它们根据所有者是类还是对象而区分开来。

类对象是共享的 —— 它们可以被一个类的所有实例存取。即一个类对象在类中只存在一个拷贝,任何对象对其的修改都会反映到所有对象上。

而每个独立的类对象/实例都拥有自己的对象变量。这样每个对象都有属于自己的字段拷贝即这些字段非共享,不同对象中的同名对象变量彼此没有任何关联。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#!/usr/bin/python
# Filename: objvar.py

class Robot:
'''Represents a robot, with a name.'''

# A class variable, counting the number of robots
population = 0

def __init__(self, name):
'''Initializes the data.'''
self.name = name
print('(Initializing {0})'.format(self.name))
# When this person is created, the robot
# adds to the population
Robot.population += 1

def __del__(self):
'''I am dying.'''
print('{0} is being destroyed!'.format(self.name))
Robot.population -= 1

if Robot.population == 0:
print('{0} was the last one.'.format(self.name))
else:
print('There are still {0:d} robots working.'.format(Robot.population))

def sayHi(self):
'''Greeting by the robot.

Yeah, they can do that.'''
print('Greetings, my masters call me {0}.'.format(self.name))

def howMany():
'''Prints the current population.'''
print('We have {0:d} robots.'.format(Robot.population))
howMany = staticmethod(howMany)

droid1 = Robot('R2-D2')
droid1.sayHi()
Robot.howMany()

droid2 = Robot('C-3PO')
droid2.sayHi()
Robot.howMany()

print("\nRobots can do some work here.\n")
print("Robots have finished their work. So let's destroy them.")

del droid1
del droid2
Robot.howMany()

输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(Initializing R2-D2)
Greetings, my masters call me R2-D2.
We have 1 robots.
(Initializing C-3PO)
Greetings, my masters call me C-3PO.
We have 2 robots.

Robots can do some work here.

Robots have finished their work. So let's destroy them.
R2-D2 is being destroyed!
There are still 1 robots working.
C-3PO is being destroyed!
C-3PO was the last one.
We have 0 robots.

说明

howMany实际上是一个属于类的方法而不是对象。这意味着我们可以将及其定义为classmethod也可以定义为staticmethod,这取决于我们是否需要知道我们是哪个类的一部分。因为我们无需类似信息,所以我们使用staticmethod。

另外我们还可以通过装饰符(decorators)达到同样的效果:

1
2
3
4
5
6
@staticmethod

def howMany():
'''Prints the current population.'''

print('We have {0:d} robots.'.format(Robot.population))

装饰符可以被想象成调用一条显式语句的捷径,就像在这个例中看到的一样。

本例中,我们还在类与方法中使用了文档字符串。我们可以在运行时使用Robot.__doc__Robot.sayhi.__doc__分别访问类和方法的文档字符串。

就像__init__方法,这里还有另一个特殊方法__del__,当对象被删除的时候将被调用。对象被删除是指对象不再被使用了,它占用的空间将返回给系统以便重复使用。在__del__中我们只是简单的将Robot.population减1。

当对象不再被使用时__del__方法将可以被执行,但无法保证到底啥时执行它。如果你想显式执行它则必须使用del语句,就象本例中做的那样。(注:本例中del后对象的引用计数降为0)。

C++/Java/C#程序员请注意

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#!/usr/bin/python
# Filename: inherit.py

class Parent(object):

def override(self):
print("PARENT override()")

def implicit(self):
print("PARENT implicit()")

def altered(self):
print("PARENT altered()")

class Child(Parent):

def override(self):
print("CHILD override()")

def altered(self):
print("CHILD, BEFORE PARENT altered()")
Parent.altered()
print("CHILD, AFTER PARENT altered()")

dad = Parent()
son = Child()

dad.implicit()
son.implicit()

dad.override()
son.override()

dad.altered()
son.altered()

输出

1
2
3
4
5
6
$ python inherit.py
OTHER implicit()
CHILD override()
CHILD, BEFORE OTHER altered()
OTHER altered()
CHILD, AFTER OTHER altered()

说明

为了使用继承,我们在类定义的类名后的一个元组中指定基类名。然后我们注意到使用super函数显式调用了基类的__init__方法,这样我们就能初始化对象的基类部分了。牢记这点非常重要 – Python不会自动调用基类的构造函数,你必须自己显式的调用它

同时应该注意到程序调用的是子类型的方法而不是基类的方法。理解这个机制的一个思路是python永远先在实际类型中查找被调用的方法,这个例子中就是如此。如果Python没有找到被调用方法,则会在基类中逐个查找,查找顺序由类定义时在元组中指定的基类顺序决定。

super 关键字

在上面的例子中,显示调用基类的函数是直接通过使用基类名字来完成的。例如:

1
parent.altered()

但这种做法有一个潜在性的隐患:如果以后更换了基类,则这里的名字就要更换。如果重载的函数很多,修改起来就很麻烦。使用super函数可以解决这个问题:

1
super(Child, self).altered()

super函数传入的参数是当前子类的类名, python会为其找到相应的基类,然后调用相应方法。

从 Python3 开始,super函数被进一步简化了,可以不再给super传参数:

1
super().altered()

组合(聚合)

还有另外一种非常有用的方法可以达到和上面完全一样的目的,它可以允许一个类直接使用和封装来自其他类和模块中定义的接口,而不需要依靠继承。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#!/usr/bin/python
# composition.py
class Other(object):

def override(self):
print("OTHER override()")

def implicit(self):
print("OTHER implicit()")

def altered(self):
print("OTHER altered()")

class Child(object):

def __init__(self):
self.other = Other()

def implicit(self):
self.other.implicit()

def override(self):
print("CHILD override()")

def altered(self):
print("CHILD, BEFORE OTHER altered()")
self.other.altered()
print("CHILD, AFTER OTHER altered()")

son = Child()

son.implicit()
son.override()
son.altered()

输出

1
2
3
4
5
6
$ python composition.py
OTHER implicit()
CHILD override()
CHILD, BEFORE OTHER altered()
OTHER altered()
CHILD, AFTER OTHER altered()

应该选择哪个?

三条指导准则:

  1. 避免多继承,因为它会使得类之间的关系太复杂,影响可靠性。
  2. 使用组合来将代码打包,以方便在多个不同且没有关联的地方使用。
  3. 仅当在同一个概念下存在清晰、可复用的代码才使用继承,或者由于某个原因不得不使用继承的情况下才使用继承。

迭代器

迭代器就是一个定义了 __iter()__ 方法的类。例如这是一个生成Fibonacci序列的迭代器:

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Fib:                                        
def __init__(self, max):
self.max = max

def __iter__(self):
self.a = 0
self.b = 1
return self

def __next__(self):
fib = self.a
if fib > self.max:
raise StopIteration
self.a, self.b = self.b, self.a + self.b
return fib

说明

  1. __iter__()方法用于初始化迭代器,每个__iter__()方法都需要做的就是必须返回一个迭代器。
  2. 当有人调用iter(fib)的时候,__iter__()就会被调用。(正如你等下会看到的, for 循环会自动调用它 , 你也可以自己手动调用。) 在完成迭代器初始化后,(在本例中, 重置我们两个计数器 self.aself.b), __iter__() 方法能返回任何实现了 __next__() 方法的对象。 在本例(甚至大多数例子)中, __iter__() 仅简单返回 self, 因为该类实现了自己的 __next__() 方法。
  3. 当有人在迭代器的实例中调用next()方法时,__next__()会自动调用。 随后会有更多理解。
  4. __next__()方法抛出 StopIteration 异常, 这是给调用者表示迭代用完了的信号。 和大多数异常不同, 这不是错误;它是正常情况,仅表示迭代器没有值可产生了。 如果调用者是 for 循环, 它会注意到该 StopIteration 异常并优雅的退出。 (换句话说,它会吞掉该异常。) 这点神奇之处就是使用 for 的关键。

输出

1
2
3
4
>>> from fibonacci2 import Fib
>>> for n in Fib(1000):
... print(n, end=' ')
0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987

for 循环内有魔力。下面是究竟发生了什么:

  1. 如你所见,for 循环调用 Fib(1000)。 这返回 Fib 类的实例。 叫它 fib_inst
  2. 背地里,且十分聪明的, for 循环调用 iter(fib_inst), 它返回迭代器。叫它 fib_iter。 本例中, fib_iter == fib_inst , 因为 __iter__() 方法返回 self,但 for 循环不知道(也不关心)那些。
  3. 为“循环通过”迭代器, for 循环调用 next(fib_iter), 它又调用 fib_iter 对象的 __next__() 方法,产生下一个菲波拉稀计算并返回值。 for 拿到该值并赋给 n, 然后执行n值的 for 循环体。
  4. next(fib_iter) 抛出 StopIteration 异常时, for循环将吞下该异常并优雅退出。 (其他异常将传过并如常抛出。)

Comments