Python-100-Day ② Python 面向对象

Python 100 Days 的学习笔记 ② : Day 8 ~ Day 9

原项目地址

本文主要涉及:

  • 面向对象编程基本 (访问权限, 命名惯例, 调用方法)
  • 面向对象高阶:
    • @property装饰器
    • __slots__魔法
    • 静态方法和类方法
    • 类之间的关系
    • 继承与多态

面向对象编程基础

  1. 在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的
  2. 如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头, 但事实上私有属性仍可以在外界访问
  3. 一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重
  4. 面向对象有三大支柱:封装、继承和多态
  5. 对封装的理解是“隐藏一切可以隐藏的实现细节,只向外界暴露(提供)简单的编程接口”

在使用类的对象方法时, 一种方式是通过对象名调用方法, 另一种方式是通过给类发消息, 但是将需要使用的对象作为参数传给类的方法, 如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from math import sqrt


class Triangle(object):

def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c

def perimeter(self):
return self._a + self._b + self._c

a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
t = Triangle(a, b, c)

# 方法 1
print(t.perimeter())

# 方法 2
print(Triangle.perimeter(t))

面向对象进阶

@property装饰器

为了解决属性和方法的访问权限问题, 虽然可以将属性设置为私有, 但外界仍然可以直接访问, 同时无法检查属性的赋值是否符合要求.

一个就解决办法就是将属性设置成私有, 然后通过设置类装饰器实现对属性的访问和修改, 即为使用@property装饰器来包装属性的 getter(访问器)和 setter(修改器)方法, 类似于:

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
class Person(object):

def __init__(self, name, age):
self._name = name
self._age = age

# 访问器 - getter方法
@property
def name(self):
return self._name

# 访问器 - getter方法
@property
def age(self):
return self._age

# 修改器 - setter方法
@age.setter
def age(self, age):
self._age = age

def play(self):
if self._age <= 16:
print('%s正在玩飞行棋.' % self._name)
else:
print('%s正在玩斗地主.' % self._name)


def main():
person = Person('王大锤', 12)
person.play()
person.age = 22
person.play()
# person.name = '白元芳' # AttributeError: can't set attribute


if __name__ == '__main__':
main()

__slots__魔法

由于 Python 是动态语言, 因此在运行时可以给对象绑定新的属性和方法.

__slots__ 用于限制自定义类型的对象能够绑定的属性.

需要注意的是__slots__的限定只对当前类的对象生效,对子类并不起任何作用.

1
2
3
4
5
6
7
8
9
class Person(object):

# 限定Person对象只能绑定_name, _age和_gender属性
__slots__ = ('_name', '_age', '_gender')

def __init__(self, name, age):
self._name = name
self._age = age
......

静态方法和类方法

  1. 大部分情况下, 在类中定义的方法都是对象方法
  2. 但写在类中的方法并不需要都是对象方法
  3. 比如在某些情况下, 还未创建好对象时, 便需要使用类中的方法, 那么这个方法是属于类的而并不属于类的对象的
  4. 可以使用静态方法来解决这类问题, 使用装饰器 @staticmethod
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
from math import sqrt


class Triangle(object):

def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c

@staticmethod
def is_valid(a, b, c):
return a + b > c and b + c > a and a + c > b

def perimeter(self):
return self._a + self._b + self._c

def area(self):
half = self.perimeter() / 2
return sqrt(half * (half - self._a) *
(half - self._b) * (half - self._c))


def main():
a, b, c = 3, 4, 5
# 静态方法和类方法都是通过给类发消息来调用的
if Triangle.is_valid(a, b, c):
t = Triangle(a, b, c)
print(t.perimeter())
# 也可以通过给类发消息来调用对象方法但是要传入接收消息的对象作为参数
# print(Triangle.perimeter(t))
print(t.area())
# print(Triangle.area(t))
else:
print('无法构成三角形.')


if __name__ == '__main__':
main()

和静态方法比较类似,Python还可以在类中定义类方法,类方法可以在类中返回此类创建的对象.

类方法的第一个参数约定名为cls,它代表的是当前类相关的信息的对象(类本身也是一个对象,有的地方也称之为类的元数据对象),通过这个参数我们可以获取和类相关的信息并且可以创建出类的对象,代码如下所示。

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
from time import time, localtime, sleep


class Clock(object):
"""数字时钟"""

def __init__(self, hour=0, minute=0, second=0):
self._hour = hour
self._minute = minute
self._second = second

@classmethod
def now(cls):
ctime = localtime(time())
return cls(ctime.tm_hour, ctime.tm_min, ctime.tm_sec)

def run(self):
"""走字"""
self._second += 1
if self._second == 60:
self._second = 0
self._minute += 1
if self._minute == 60:
self._minute = 0
self._hour += 1
if self._hour == 24:
self._hour = 0

def show(self):
"""显示时间"""
return '%02d:%02d:%02d' % \
(self._hour, self._minute, self._second)


def main():
# 通过类方法创建对象并获取系统时间
clock = Clock.now()
while True:
print(clock.show())
sleep(1)
clock.run()


if __name__ == '__main__':
main()

类之间的关系

简单的说,类和类之间的关系有三种:is-a、has-a和use-a关系。

  • is-a关系也叫继承或泛化,比如学生和人的关系、手机和电子产品的关系都属于继承关系。
  • has-a关系通常称之为关联,比如部门和员工的关系,汽车和引擎的关系都属于关联关系;关联关系如果是整体和部分的关联,那么我们称之为聚合关系;如果整体进一步负责了部分的生命周期(整体和部分是不可分割的,同时同在也同时消亡),那么这种就是最强的关联关系,我们称之为合成关系。
  • use-a关系通常称之为依赖,比如司机有一个驾驶的行为(方法),其中(的参数)使用到了汽车,那么司机和汽车的关系就是依赖关系。

类之间的关系

继承与多态

子类在继承了父类的方法后,可以对父类已有的方法给出新的实现版本,这个动作称之为方法重写(override)。通过方法重写我们可以让父类的同一个行为在子类中拥有不同的实现版本,当我们调用这个经过子类重写的方法时,不同的子类对象会表现出不同的行为,这个就是多态(poly-morphism)。

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
from abc import ABCMeta, abstractmethod


class Pet(object, metaclass=ABCMeta):
"""宠物"""

def __init__(self, nickname):
self._nickname = nickname

@abstractmethod
def make_voice(self):
"""发出声音"""
pass


class Dog(Pet):
"""狗"""

def make_voice(self):
print('%s: 汪汪汪...' % self._nickname)


class Cat(Pet):
"""猫"""

def make_voice(self):
print('%s: 喵...喵...' % self._nickname)


def main():
pets = [Dog('旺财'), Cat('凯蒂'), Dog('大黄')]
for pet in pets:
pet.make_voice()


if __name__ == '__main__':
main()

在上面的代码中,我们将Pet类处理成了一个抽象类,所谓抽象类就是不能够创建对象的类,这种类的存在就是专门为了让其他类去继承它。Python从语法层面并没有像Java或C#那样提供对抽象类的支持,但是我们可以通过 abc 模块的 ABCMeta 元类和 abstractmethod 装饰器来达到抽象类的效果,如果一个类中存在抽象方法那么这个类就不能够实例化(创建对象)。上面的代码中,DogCat两个子类分别对Pet类中的 make_voice 抽象方法进行了重写并给出了不同的实现版本,当我们在 main 函数中调用该方法时,这个方法就表现出了多态行为(同样的方法做了不同的事情)。

Python-100-Day ② Python 面向对象

http://blog.czccc.cc/p/24e7dd40/

作者

Cheng

发布于

2019-06-10

更新于

2022-08-06

许可协议

评论