0%

Python - Theme 6 OOP

OOP- Object Oriented Programming. 把对象作为程序的基本单元,一个对象包含数据和函数,计算机程序执行就是消息在各个对象之间的传递。

1. 类和实例及访问限制

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
#!/usr/bin/env python
# -*- coding : utf-8 -*-

#Student类
class Student(object): #object类--所有类最终都会继承的类, 这里可以将object看做父类

def __init__(self, name, score): #创建实例的时候需要传入的参数,会绑定给实例
self.__name = name #__name, 两个下划线开头表示私有变量,外部无法访问
self.score = score

def get_name(self): #由于外部无法访问私有变量, 因此通过内部方法来实现;
return self.__name

def set_name(self, name): #这样的好处在于可以规定传入参数的要求
self.__name = name

def print_score(self):
print('%s: %s' % (self.__name, self.score))

def get_grade(self):
if self.score >= 90:
return 'A'
elif self.score >= 60:
return 'B'
else:
return 'C'

#输出
>>> nick = Student('Nick Yang', 50) #生成一个名为nick的实例, 变量nick指向这个实例.
>>> nick
<__main__.Student object at 0x10d4c1090>
>>> Student
<class '__main__.Student'>
>>> nick.name #私有变量不能通过外部访问
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'name'
>>> nick.get_name()
'Nick Yang'
>>> nick.print_score()
Nick Yang: 50
>>> nick.score #score不是私有变量, 因此可以直接访问
50
>>> nick.score = 60 #可直接赋新值
>>> nick.score
60
>>> nick.get_grade()
'B'
>>> nick.age = 18 #可以自由的给实例绑定属性
>>> nick.age
18

2. 继承和多态

多态:使用单一接口操作具有相似特性数据的能力

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
53
54
55
56
57
58
59
60
61
#!/usr/bin/env python
# -*- coding : utf-8 -*-


class Ielts(object):
def __init__(self):
self.__totalScore = 9
self.bringItems = ['ID document', 'a pencil and a pen', 'an eraser']

def print_total_score(self):
print('Total_score: %s' % self.__totalScore)

def print_bring_items(self):
print('Necessary_Items: %s' % self.bringItems)

def exam(self):
print('It is IELTS exam.')


class Reading(Ielts):
def exam(self):
print('It is Reading exam.')


class Writing(Ielts):
def exam(self):
print('It is Writing exam.')


class Listening(Ielts):
def exam(self):
print('It is Listening exam.')


class Speaking(Ielts):
def __init__(self):
self.bringItems = ['ID document']

def exam(self):
print('It is Speaking exam.')

def exam(obj):
obj.exam() #多态: 让不同功能的函数使用相同的函数名, 这样可以用一个函数名来调用不同功能(内容)的函数.

examination = Ielts()
examination.exam()
examination.print_bring_items()

speakingExam = Speaking()
speakingExam.print_bring_items() #继承了父类的方法
speakingExam.exam()
exam(speakingExam)
#speakingExam.print_total_score() #AttributeError: 'Speaking' object has no attribute '_Ielts__totalScore', 子类不会继承父类的私有变量


#输出
It is IELTS exam.
Necessary_Items: ['ID document', 'a pencil and a pen', 'an eraser']
Necessary_Items: ['ID document']
It is Speaking exam.
It is Speaking exam.

3. 获取对象信息

3.1. type()

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
#判断基本类型
>>> type(10)
<class 'int'>
>>> type('10')
<class 'str'>
>>> type([1,2,3])
<class 'list'>
>>> type((1,2,3))
<class 'tuple'>
>>> type(None)
<class 'NoneType'>
>>> type(abs)
<class 'builtin_function_or_method'>

#判断函数类型
>>> import types
>>> def f():
... pass
...
>>> type(f)
<class 'function'>
>>> type(f) == types.FunctionType
True
>>> type(abs) == types.FunctionType
False
>>> type(abs) == types.BuiltinFunctionType
True
>>> type(lambda x:x) == types.LambdaType
True
>>> type((x for x in range(10))) == types.GeneratorType
True

3.2. isinstance()

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
#type()能判断的,isinstance()也可以, 但是应用场景不同
>>> isinstance('10', str)
True
>>> isinstance(10, int)
True
>>> isinstance([1,2,3], list)
True

#判断class的类型, 尤其是在继承关系中
>>> class Animal(object):
... pass
... class Cat(Animal):
... pass
... class Kitty(Cat):
... pass
...
>>> a = Animal()
>>> c = Cat()
>>> k = Kitty()
>>> isinstance(k, Cat) and isinstance(k, Animal)
>>> True

#可以判断变量是否属于某些类型
>>> isinstance((1,2,3), (list, tuple))
True

3.3. dir()

获取一个对象的所有属性和方法

1
2
3
4
5
6
>>> dir('abc')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__'......]
>>> len('abc') #实际上是去调用了对象的__Len__()方法
3
>>> 'abc'.__len__()
3

4. 实例属性和类属性

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
#通常使用self变量或者实例变量来给实例绑定属性; 而这种直接在类中定义的属性为类属性;
>>> class Test(object):
... x = 1
...
>>> t = Test()
>>> t.x #因为实例没有x属性, 所以会向上查找class的x属性
1
>>> Test.x #类属性
1
>>> t.x = 2 #给实例绑定x属性
>>> t.x #实例属性优先
2
>>> Test.x #类属性不变
1
>>> del t.x #删除实例属性
>>> t.x
1

#每创建一个学生实例,则计数一次
#!/usr/bin/env python
# -*- coding : utf-8 -*-


class Student(object):
count = 0

def __init__(self, name):
self.name = name
Student.count += 1


tom = Student('Tom')
bob = Student('Bob')
print('Student.count = ', Student.count)

#输出
Student.count = 2

5. slots 限制实例属性

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
53
54
55
56
57
58
59
60
61
62
63
#!/usr/bin/env python
# -*- coding : utf-8 -*-


class Student(object):
__slots__ = ('__name', 'gender', 'age')

def __init__(self, name):
self.__name = name

def get_name(self):
return self.__name


class Master(Student):
pass


class Doctor(Student):
__slots__ = ('major',)

#输出
>>> s = Student('Tom')
>>> s.get_name()
'Tom'
>>> s.age = 18
>>> s.score = 100 #因为加了__slots__限制
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

#__slots__限制不继承
>>> m = Master('Tom')
>>> m.score = 100

#如果子类也有__slots__, 则会继承
>>> d = Doctor('Tom')
>>> d.score = 100
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Doctor' object has no attribute 'score'
>>> d.age = 28
>>> d.major = 'CS'

#类方法也同理
>>> def set_age(self, age):
... self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s)
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'set_age'

>>> m.set_age = MethodType(set_age, m)
>>> m.set_age(18)
>>> m.age
18

>>> d.set_age = MethodType(set_age, d)
Traceback (most recent call last):
File "<input>", line 1, in <module>
AttributeError: 'Doctor' object has no attribute 'set_age'

6. @property 像访问属性一样的访问类方法,且约束访问权限

6.1. 属性无法被保护,且没有参数检测

1
2
3
4
5
6
7
8
9
10
11
12
class Screen(object):
def __init__(self, width, height):
self._width = width
self._height = height
self.resolution = self._width * self._height

#输出
>>> s = Screen(1024,768)
>>> s._width
1024
>>> s.resolution
786432

6.2. 属性被保护了,但是访问属性的方式变了 – Method

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
class Screen(object):
def __init__(self, width, height):
self.__width = width
self.__height = height

def get_width(self):
return self.__width

def set_width(self, value):
if value < 0:
print('the value of width is wrong')
else:
self.__width = value

def get_height(self):
return self.__height

def set_height(self, value):
if value < 0:
print('the value of height is wrong')
else:
self.__height = value

def resolution(self):
return self.__width * self.__height


#输出
>>> s = Screen(1024, 786)
>>> s.get_width()
1024
>>> s.set_width(1080)
>>> s.get_width()
1080
>>> s.resolution()
848880

6.3. 利用property

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
#!/usr/bin/env python
# -*- coding : utf-8 -*-


class Screen(object):
def __init__(self, width, height):
self.__width = width
self.__height = height

@property #getter方法
def width(self):
return self.__width

@width.setter #setter方法
def width(self, value):
if value < 0:
print('the value of width is wrong')
else:
self.__width = value

def get_height(self):
return self.__height

def set_height(self, value):
if value < 0:
print('the value of height is wrong')
else:
self.__height = value

height = property(fget=get_height, fset=set_height) #利用了property类的原型, 这样也不用修改函数名了

@property
def resolution(self):
return self.__width * self.__height


#输出
>>> s = Screen(1024, 768)
>>> s.width
1024
>>> s.height
768
>>> s.width = 1080
>>> s.resolution
829440

6.4. property类原型

1
def __init__(self, fget=None, fset=None, fdel=None, doc=None)

7. 多重继承与MixIn设计

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
#!/usr/bin/env python
# -*- coding : utf-8 -*-


class Transportation(object):
pass


class Public(Transportation):
pass


class Private(Transportation):
pass


class RoadMixIn(object):
def road(self):
print('On the road')


class SeaMixIn(object):
def sea(self):
print('On the sea')


class SkyMixIn(object):
def sky(self):
print('In the sky')


class Bus(Public, RoadMixIn):
pass


class Car(Private, RoadMixIn):
pass


class Airplane(Public, SkyMixIn):
pass


class Steamship(Public, SeaMixIn):
pass


#输出
>>> c = Car()
>>> c.road()
On the road
>>> a = Airplane()
>>> a.sky()
In the sky
>>> s = Steamship()
>>> s.sea()
On the sea


#实际应用
(1).多进程TCP服务
class MyTCPServer(TCPServer, ForkingMixIn):
pass
(2).多线程UDP服务
class MyUDPServer(UDPServer, ThreadingMixIn):
pass

8. 定制类

8.1. strrepr 描述对象信息

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#__str__  : 强调可读性,结果是让人看的
#__repr__ : 强调准确性,结果是让解释器用的. (默认内置类型)
>>> import datetime
>>> today = datetime.datetime.now()
>>> today
datetime.datetime(2020, 5, 19, 11, 9, 14, 161591)
>>> repr(today)
'datetime.datetime(2020, 5, 19, 11, 9, 14, 161591)'
>>> str(today)
'2020-05-19 11:09:14.161591'


#__str__
>>> str('4')
'4'
>>> '4'.__str__()
'4'
>>> str(4)
'4'


#__repr__
>>> repr('4')
"'4'"
>>> '4'.__repr__()
"'4'"
>>> repr(4)
'4'

#关系
>>> str(4) == repr(4) #整数的返回值是一致的
True
>>> str('4') == repr('4') #字符串的返回值不同
False

#原始状态
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
>>> s = Student('Mike')
>>> s
<__main__.Student object at 0x10281ba50>
>>> print(s)
<__main__.Student object at 0x10281ba50>

#重写了__str__后,在调用print函数时生效
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
... def __str__(self):
... return 'Student object (name: %s)' % self.name
...
>>> s = Student('Mike')
>>> s
<__main__.Student object at 0x102831250>
>>> print(s)
Student object (name: Mike)

#重写了__repr__后, 交互模式和print函数均生效
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
... def __repr__(self):
... return 'Student object (name: %s)' % self.name
...
>>> s = Student('Mike')
>>> s
Student object (name: Mike)
>>> print(s)
Student object (name: Mike)

8.2. iter

如果一个类想被用于for循环就必须实现一个iter()方法,该方法返回一个迭代对象,for循环会不断的调用该迭代对象的next()方法,直到遇到StopIteration退出。

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
>>> class Fib(object):
... def __init__(self):
... self.a = 0
... self.b = 1
...
... def __iter__(self):
... return self
...
... def __next__(self):
... self.a, self.b = self.b, self.a + self.b
... if self.a > 100:
... raise StopIteration()
... return self.a
...
>>> f = Fib()
>>> f
<__main__.Fib object at 0x10bd40b50>
>>> type(f)
<class '__main__.Fib'>
>>> for i in f:
... print(i)
...
1
1
2
3
5
8
13
21
34
55
89

8.3. getitem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#通过__iter__()实现的例子虽然能用于for循环,但是却无法像List一样的使用
>>> Fib()[3]
Traceback (most recent call last):
File "<input>", line 1, in <module>
TypeError: 'Fib' object is not subscriptable

#__getitem__()
>>> class Fib(object):
... def __getitem__(self, n):
... a, b = 1, 1
... for x in range(n):
... a, b = b, a + b
... return a
...
>>> Fib()[3]
3

#如果想在类中实现List,dict,tuple等,可以重写__getitem__、__setitem__、__delitem__

8.4. getattr

可以用于把一个类的属性和方法动态化处理

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
#如果找不到类属性or类方法, 则会去__getattr__中寻找; 因此加了判断。通过这种方法来实现动态。
>>> class Student(object):
...
... def __init__(self):
... self.name = 'Michael'
...
... def __getattr__(self, attr):
... if attr == 'score':
... return 100
... raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
...
>>> s = Student()
>>> s.name
'Michael'
>>> s.score
100
>>> s.age
Traceback (most recent call last):
File "<input>", line 1, in <module>
File "<input>", line 9, in __getattr__
AttributeError: 'Student' object has no attribute 'age'


#链式调用: REST API; 这样就不需要给每一个URL的API都写一个方法了
#Eg. http://api.server/user/timeline/list
class Chain(object):

def __init__(self, path=''):
self._path = path

def __getattr__(self, path):
return Chain('%s/%s' % (self._path, path))

def __str__(self):
return self._path

__repr__ = __str__


print(Chain().status.user.timeline.list)

#输出
/status/user/timeline/list

8.5. call 实现类实例本身的调用

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
>>> class Student(object):
... def __init__(self, name):
... self.name = name
...
... def __call__(self):
... print('My name is %s.' % self.name)
...
>>> s = Student('Mike')
>>> s() #像函数一样调用
My name is Mike.

#判断一个对象是否能被调用
>>> callable(Student('Mike'))
True
>>> callable(abs)
True
>>> callable([1,2,3])
False
>>> callable('str')
False

#实现 Chain().users('michael').repos的链式调用
class Chain(object):
def __init__(self, path=''):
self.__path = path

def __getattr__(self, path):
return Chain('%s/%s' % (self.__path, path))

def __call__(self, path):
return Chain('%s/%s' % (self.__path, path))

def __str__(self):
return self.__path

__repr__ = __str__


print(Chain().users('michael').repos)

9. 枚举类

枚举类型可以看做是一种标签或一组常量集合,例如:月份、星期、状态等。
字典和类也可实现,但是存在几个问题:

  • 变量可更改
  • 字符串所占内存比数字大

    9.1. 使用枚举类

    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
    53
    54
    55
    56
    #使用字典
    Week = {
    'Sun': 0,
    'Mon': 1,
    'Tue': 2,
    'Wed': 3,
    'Thu': 4,
    'Fri': 5,
    'Sat': 6,
    }
    #使用类
    class Week(object):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

    #使用 枚举类
    from enum import Enum
    #(1).方式一
    class Week(Enum):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

    for d in Week:
    print(d.name, '=', d.value)
    #输出
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6


    #(2).方式二
    Week = Enum('Week', ('Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'))
    for name, member in Week.__members__.items():
    print(name, '=>', member, ',', member.value - 1)
    #输出
    Sun => Week.Sun , 0
    Mon => Week.Mon , 1
    Tue => Week.Tue , 2
    Wed => Week.Wed , 3
    Thu => Week.Thu , 4
    Fri => Week.Fri , 5
    Sat => Week.Sat , 6

9.2. 定义枚举类

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
#成员名(key)不能重复
class Week(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
Sun = 10 #Attempted to reuse key: 'Sun'

#成员值(value)可以重复,但是相当于别名
class Week(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
Hol = 0

>>> print(Week.Sun)
Week.Sun
>>> print(Week.Hol) #相当于别名
Week.Sun
>>> print(Week.Sun is Week.Hol)
True
>>> print(Week(0)) #虽然值一样,但只会显示第一个
Week.Sun

#unique可使成员值(value)不可重复
from enum import Enum, unique
@unique
class Week(Enum):
Sun = 0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
Hol = 0 #ValueError: duplicate values found in <enum 'Week'>: Hol -> Sun

9.3. 枚举取值

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
#通过成员名
>>> print(Week.Sat)
Week.Sat
>>> print(Week['Sat'])
Week.Sat

#通过成员值
>>> print(Week(6))
Week.Sat

#成员均有name和value属性
>>> print(Week.Sat.name)
Sat
>>> print(Week.Sat.value)
6

#遍历
#(1).直接遍历,如果有重复值成员,则只显示第一个
>>> for d in Week:
... print(d)
...
Week.Sun
Week.Mon
Week.Tue
Week.Wed
Week.Thu
Week.Fri
Week.Sat
#(2).__members__ 特殊属性,是一个将名称映射到成员的有序字典,显示全部成员
>>> for d in Week.__members__.items():
... print(d)
...
('Sun', <Week.Sun: 0>)
('Mon', <Week.Mon: 1>)
('Tue', <Week.Tue: 2>)
('Wed', <Week.Wed: 3>)
('Thu', <Week.Thu: 4>)
('Fri', <Week.Fri: 5>)
('Sat', <Week.Sat: 6>)
('Hol', <Week.Sun: 0>) #重复值成员在此

9.4. 枚举比较

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#is: 同一性比较
>>> Week.Sun is Week.Hol
True
>>> Week.Sun is Week.Sat
False

#==: 等值比较
>>> Week.Sun == Week.Hol
True
>>> Week.Sun != Week.Sat
True

#大小比较
>>> Week.Sun < Week.Sat
TypeError: '<' not supported between instances of 'Week' and 'Week'

9.5. IntEnum,扩展类,支持比较

1
2
3
4
5
6
7
8
9
10
11
12
>>> from enum import IntEnum
>>> class Week(IntEnum):
... Sun = 0
... Mon = 1
... Tue = 2
... Wed = 3
... Thu = 4
... Fri = 5
... Sat = 6
...
>>> Week.Sun < Week.Sat
True

10. 元类

ToDo