JY's Den

想了好久


  • 首页

  • 标签

  • 分类

  • 归档

Swift CustomStringConvertible

发表于 2017-02-10 |

自定义输出,必须遵循 CustomStringConvertible 协议,实现 description 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Person: CustomStringConvertible  {
var name: String
var age: Int

init(name:String, age:Int) {
self.name = name
self.age = age
}
var description: String{
return name + String(age)
}
}

let person = Person(name: "JY", age: 25)
print(person)

Cocoa中常见的宏命令

发表于 2017-02-09 |

###参考
常见宏

算法

发表于 2017-02-04 |

算法是每一个程序员都必须要认真学习的一门课程,虽然在工作中很少能够用到,但是它能够让我们理解程序的世界。由于前段时间学习了Python,Python语法简洁,便于理解。如果看不明白,可以借助这个网站来帮助理解。

冒泡排序

冒泡排序很简单,重复的遍历数列,依次比较两个元素,满足条件时,将两个元素进行互换。

思路:

  1. 比较相邻的两个元素,如果第一个元素比第二个元素大,就交换两个元素(里层条件判断)。
  2. 对第0个元素到第n-i个元素做同样的工作,这时候最大的数就会在数组中最后的位置(外层循环)。
  3. 对所有的元素重复上面两个步骤
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def bubble_sort(array):
n = len(array)
for i in range(n):
for j in range(n - i - 1):
if array[j] > array[j + 1]:
array[j], array[j + 1] = array[j + 1], array[j]
return array


if __name__ == '__main__':
s = bubble_sort([3, 2, 4, 5, 1])
print (s)

快速排序

快排通常比其他的算法快,因此常常被采用。快排算法体现了分治法的思想。

思路:

  1. 从数列当中取出一个元素作为基准数。
  2. 将比基准数大的放到右边,小于或等于它的数都放在左边
  3. 再对左右区间递归执行步骤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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def quick_sort(ary):
return qsort(ary, 0, len(ary) - 1)


def qsort(ary, left, right):
# ary为待排序数组,left为待排序的左边界,right为右边界
if left >= right: return ary
key = ary[left] # 基准数
left_ptr, right_ptr = left, right
while left_ptr < right_ptr:
while ary[right_ptr] >= key and left_ptr < right_ptr:
right_ptr -= 1
while ary[left_ptr] <= key and left_ptr < right_ptr:
left_ptr += 1
# 交换两个指针对应的值
ary[left_ptr], ary[right_ptr] = ary[right_ptr], ary[left_ptr]
ary[left], ary[left_ptr] = ary[left_ptr], ary[left]
qsort(ary, left, left_ptr - 1)
qsort(ary, right_ptr + 1, right)
return ary


if __name__ == '__main__':
s = quick_sort([3, 2, 4, 5, 1])
print (s)

插入排序

对于每个未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

思路:

  1. 从第一个元素开始,该元素可以认为已经被排序
  2. 取出下一个元素,在已经排序的元素序列中从后向前扫描
  3. 如果被扫描的元素(已排序)大于新元素,将该元素后移一位
  4. 重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
  5. 将新元素插入到该位置后
  6. 重复步骤2~5
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def insert_sort(array):
n = len(array)
for i in range(1, n):
if array[i] < array[i - 1]:
temp = array[i] # 取出待插入的元素
index = i # 取出待插入元素的下标
# 从后往前扫描,从 i-1 循环到 0 (包括0)
for j in range(i - 1, -1, -1):
if array[j] > temp:
array[j + 1] = array[j] # 将该扫描的元素向后移动一位
index = j # 记录待插入的下标
else:
break
array[index] = temp
print(array)
return array


if __name__ == '__main__':
s = insert_sort([3, 2, 4, 5, 1])
print (s)

选择排序

对于一个列表l = {k1, k2, … kn},将其从小到大排序。

思路:

  1. 先从列表l,并选择最小的值minNum,将minNum与k1对换。
  2. 然后遍历剩下的元素(即k2, … kn)选取最小值minNum,与k2交换
  3. 以此类推,知道所有元素均排序完毕
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def select_sort(ary):
n = len(ary)
for i in range(n):
# 记录最小元素下标
minNum = i
for j in range(i + 1, n):
if ary[j] < ary[minNum]:
# 最小的下标
minNum = j
ary[minNum], ary[i] = ary[i], ary[minNum]
return ary


if __name__ == '__main__':
s = select_sort([3, 1, 2, 4, 6])
print(s)

归并排序

归并排布是采用分治法的一个非常典型的应用。归并排序的思想就是先递归分解数组,然后再合并数组。

例如:有数列[5, 10, 2, 4, 6],第一次递归后,

思路:

先考虑合并两个有序数组:思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。

再考虑递归分解:思路是将数组分解成left和right,如果这两个数组内部数据是有序的,那么就可以用上面合并数组的方法将这两个数组合并排序。如何让这两个数组内部是有序的?可以再二分,直至分解出的小组只含有一个元素时为止,此时认为该小组内部已有序。然后合并排序相邻二个小组即可。

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

def merge_sort(ary):
if len(ary) <= 1: return ary
# 二分分解
num = int(len(ary) / 2)
left = merge_sort(ary[:num])
right = merge_sort(ary[num:])

# 合并数组
return merge(left, right)

def merge(left, right):
print ('left = ', left)
print ('right = ', right)
'''
将两个有序数组left[]和right[]合并成一个大的有序数组
'''
# left和right数组的下标指针
l,r = 0,0
result = []
while l<len(left) and r<len(right) :
if left[l] < right[r]:
result.append(left[l])
l += 1
else:
result.append(right[r])
r += 1

result += left[l:]
result += right[r:]

return result

if __name__ == '__main__':
s = merge_sort([5, 4, 3, 2, 1])
print ('sort result = ', s)

希尔排序

希尔排序的基本思想是:将数组中的数据列在一个按照一定的增量列在一个表当中,然后分别进行插入排序。一直重复这个过程,每次都改变这个增量的长度。最后这个表只有一列。

思路:有以下的数组[3, 2, 4, 5, 1, 6, 8, 7],将增量列为数组长度的一半,那么得到的表为:

1
2
3  2  4  5
1 6 8 7

然后对每列进行排序,如下表:

1
2
1  2  4  5
3 6 8 7

再次将增量依次折半,并按照下表进行排序,如下表:

1
2
3
4
1  2
3 5
4 6
8 7

当增量为1的时候,就是插入排序了,重复多次后,当增量为小于0的时候,排序完成,就得到排序后的数组列表。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def shell_sort(ary):
n = len(ary)
# 初始增量,round四舍五入取整
gap = int(round(n / 2))
while gap > 0:
for i in range(gap, n):
temp = ary[i]
j = i
while (j >= gap and ary[j - gap] > temp):
# 插入排序
ary[j] = ary[j - gap]
j = j - gap
ary[j] = temp
gap = int(round(gap / 2))
return ary


if __name__ == '__main__':
s = shell_sort([3, 2, 4, 5, 1, 6, 8, 7])
print (s)

堆排序

堆排序是采用二叉堆来的数据结构实现的,实质上还是一维数组。二叉堆是一个近似完全二叉树。

二叉堆有以下性质:

  1. 父节点的键值总是大于或等于(小于或等于)任何一个子节点的键值
  2. 每个节点的左右子树都是一个二叉堆(都是最大堆或最小堆)

思路:

  1. 构造最大堆(Build_Max_Heap):若数组下标范围为0~n,考虑到单独一个元素是大根堆,则从下标n/2开始的元素均为大根堆。于是只要从n/2-1开始,向前依次构造大根堆,这样就能保证,构造到某个节点时,它的左右子树都已经是大根堆。
  2. 堆排序(HeapSort):由于堆是用数组模拟的。得到一个大根堆后,数组内部并不是有序的。因此需要将堆化数组有序化。思想是移除根节点,并做最大堆调整的递归运算。第一次将heap[0]与heap[n-1]交换,再对heap[0…n-2]做最大堆调整。第二次将heap[0]与heap[n-2]交换,再对heap[0…n-3]做最大堆调整。重复该操作直至heap[0]和heap[1]交换。由于每次都是将最大的数并入到后面的有序区间,故操作完后整个数组就是有序的了。
  3. 最大堆调整(Max_Heapify):该方法是提供给上述两个过程调用的。目的是将堆的末端子节点作调整,使得子节点永远小于父节点 。
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
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def heap_sort(ary):
n = len(ary)
# 最后一个非叶子节点
first = int(n / 2 - 1)
# 构造大根堆
for start in range(first, -1, -1):
max_heapify(ary, start, n - 1)
for end in range(n - 1, 0, -1):
# 堆排,将大根堆转换成有序数组
ary[end], ary[0] = ary[0], ary[end]
max_heapify(ary, 0, end - 1)
return ary


# 最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
# start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary, start, end):
root = start
while True:
# 调整节点的子节点
child = root * 2 + 1
if child > end: break
if child + 1 <= end and ary[child] < ary[child + 1]:
# 取较大的子节点
child = child + 1
# 较大的子节点成为父节点
if ary[root] < ary[child]:
# 交换
ary[root], ary[child] = ary[child], ary[root]
root = child
else:
break


if __name__ == '__main__':
s = heap_sort([3, 2, 4, 5, 1])
print (s)

时间复杂度与空间复杂度

算法的时间复杂度和空间复杂度可参考这里

排序算法的时间复杂度和空间复杂度对比表:

参考

堆与堆排序

Python中异步编程

发表于 2016-12-24 |

###

Python中的多进程

发表于 2016-12-23 |

操作系统多任务的普及,跟物理设备的性能的提升有着很大的关系。这一切都归功于CPU的并发处理能力,在多核快速发展的今天,CPU的计算速度越来越快。CPU以轮询的方式处理不同的任务,这样保证了在操作系统层面上优异的用户体验。可以说多任务是迄今为止最伟大的创举,使得生产力产生质的飞越。Python的os模块封装了常见的系统调用。

Unix/Linux操作系统内核中提供了fork()系统调用,操作系统会自动将当前进程(父进程)复制一份(子进程),然后分别在父进程和子进程中返回。子进程永远返回0,父进程返回子进程的ID,子进程通过getppid()接可以拿到父进程的ID。

1
2


Python中的线程和进程

发表于 2016-12-23 |

线程和进程是计算机科学中一个非常重要的概念。比如执行一个应用程序,它就是一个独立的进程。这个应用程序里面会执行不同的任务,这些任务是由线程来完成的。一个进程至少有一个线程来完成应用程序的功能。多进程意味着可以创建多个互相之间独立的应用程序,多线程则意味着一个独立的进程当中可以同时执行不同的任务。

多进程

Unix/Linux系统提供了一个fork()系统函数调用,当调用该函数,操作系统自动将当前进程(父进程)复制一份(子进程),然后分别在父进程和子进程当中返回。子进程当中永远返回0,父进程则返回子进程的ID,这样父进程就可以知道fork出来的子进程的ID。子进程可以通过getpid()函数获取到父进程的ID。这些常见的系统调用封装在Python的os模块中:

多线程

Python中的标准库提供了_thread和threading模块两个线程模块,_thread是低级模块,threading是高级模块,它是对_thread模块进行了封装:

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
import time, threading

def test_thread():
# current_thread() 返回当前的线程的实例
print('thread %s is running...' % threading.current_thread().name)
n = 0
while n < 5:
n = n + 1
print('thread %s >>> %s' % (threading.current_thread().name, n))
time.sleep(1)
print('thread %s ended.' % threading.current_thread().name)

print('thread %s is running...' % threading.current_thread().name)
# 创建名字为 LoopThread 新的线程
t = threading.Thread(target=test_thread, name='LoopThread')
t.start()
t.join()
print('thread %s ended.' % threading.current_thread().name)

# 输出
thread LoopThread is running...
>>> t.jothread LoopThread >>> 1
in()
thread LoopThread >>> 2
thread LoopThread >>> 3
thread LoopThread >>> 4
thread LoopThread >>> 5
thread LoopThread ended.
>>> print('thread %s ended.' % threading.current_thread().name)

锁

在使用多线程的时候,由于所有的变量都可以被任意线程访问,所有则该变量有可能被任何线程修改。

为了防止变量被任意线程更改,可以利用threading模块的Lock()实现。使用lock = threading.Lock()创建一个锁,使用lock.acquire()来将需要只能单一线程访问的资源加锁,这样别的线程就不能访问。访问紫竹院结束,使用lock.release()来释放锁。通常为了避免出现死锁的现象,我们可以使用try...finally...保证锁一定能被释放:

1
2
3
4
5
6
7
8
9
10
11
import threading

lock = threading.Lock()

try:
# do something in here ...
# 获取锁
lock.acquire()
finally:
# 释放锁
lock.release()

Python中IO编程

发表于 2016-12-22 |

文件操作

Python封装操作系统读写文件的API,用法与C兼容。进行文件读写的时候,由于操作磁盘上文件的行为都是由操作系统完成的,所以需要请求操作系统打开文件对象(也称之为文件描述符),接着通过操作系统提供的接口从文件对象中读取或写入数据。

读取文件

Python内置open()函数可以读取文件,如果文件不存在则抛出IOError:

1
2
3
4
5
6
7
# r 表示 只读
>>> f = open('/User/1.txt', 'r')

# 如果文件不存在,则抛出错误,并说明出错原因
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
FileNotFoundError: [Errno 2] No such file or directory: '/User/1.txt'

如果文件打开成功,接着可以调用read()函数一次读取文件全部内容,并返回一个str对象。要记住:在文件使用完毕后必须要进行关闭:

1
2
>>> f.read()
>>> f.close()

在进行文件操作的时候,都有可能产生IOError错误,我们也可以捕获这个错误:

1
2
3
4
5
6
7
8
try:
f = open('/User/1.txt', 'r')
print(f.read())
finally:
if f:
f.close()
except Exception as e:
print(e)

Python中使用with语句来简化调用形式,可以自动调用close()方法:

1
2
with open('/User/1.txt', 'r') as f:
print(f.read())

常用的读取文件的函数:

  • read()函数一次性读取文件全部内容
  • read(size)函数每次最多读取 size 个字节
  • readline()函数每次读取一行内容
  • readlines()函数一次读取所有内容并按行返回list

读取二进制文件:

1
2
3
4
5
# 读取视频、图片等二进制文件
>>> f = open('/Users/test.jpg', 'rb')
>>> f.read()

b'\x00\xd7...' # 十六进制

字符串编码:

open()函数还可以接受两个参数,第一个参数是将读取的字符编码进行编码,如果文件中出现非法编码的字符串,会抛出UnicodeDecodeError错误。这种情况,还可以接受第二个参数:errors。可以直接忽略编码出现的错误:

1
f = open('/Users/1.txt', 'r', encoding='gbk', errors='ignore')

写文件

写操作和读操作差不多,主要区别是open()函数的文件权限标示符不一致:w或wb表示写文本文件或写二进制文件。当然,写操作完成一定要调用close()来关闭文件,也可以像上面那样使用with语句。

1
2
with open('/Users/test.txt', 'w') as f:
f.write('Hello, world!')

如果想写入特定编码的文本,可以给open()函数传入encoding参数将字符串转为指定编码。

读写内存中的文件

StringIO

在内当中读写字符串:

1
2
3
4
5
6
7
8
9
10
11
12
>>> from io import StringIO
>>> f = StringIO()
>>> f.write('hello')
5
>>> f.write(' ')
1
>>> f.write('world!')
6

# 获取写入的字符串
>>> print(f.getvalue())
hello world!

也可以用字符串初始化一个StringIO,然后,像读文件一样读取:

1
2
3
4
5
6
7
8
9
10
>>> from io import StringIO
>>> f = StringIO('Hello world!')
>>> while True:
... s = f.readline()
... if s == '':
... break
... print(s.strip())
...

Hello world!

BytesIO

读取内存中的二进制数据:

1
2
3
4
5
6
7
>>> from io import BytesIO
>>> f = BytesIO()
# 这里写入的是 utf-8 编码的二进制数据
>>> f.write('中文'.encode('utf-8'))
6
>>> print(f.getvalue())
b'\xe4\xb8\xad\xe6\x96\x87'

用bytes初始化BytesIO,然后读取它:

1
2
3
4
>>> from io import BytesIO
>>> f = BytesIO(b'\xe4\xb8\xad\xe6\x96\x87')
>>> f.read()
b'\xe4\xb8\xad\xe6\x96\x87'

操作文件

Python内置的os模块可以直接调用操作系统提供的接口,只需要import os模块:

1
2
3
4
5
6
7
8
9
>>> import os
# 操作系统类型:mac OS 是 posix,windows 是 nt
>>> os.name
# 获取系统信息,windows上没有
>>> os.uname()
# 获取操作系统环境变量
>>> os.environ
# 获取某个环境变量(PATH)的值
>>> os.environ.get('PATH')

操作文件和目录

操作文件和目录一般在os和os.path模块中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 查看蛋清目录的绝对路径
# 查看当前目录的绝对路径:
>>> os.path.abspath('.')
'/Users/mac'
# 在某个目录下创建一个新目录,并返回完整路径,在合成两个路径的时候,推荐使用这种方式合成路径
>>> os.path.join('/Users/mac/Desktop', 'TestDir')
'/Users/mac/Desktop/TestDir'
# 在当前路径创建一个目录:
>>> os.mkdir('/Users/mac/Desktop/TestDir')
# 删掉一个目录:
>>> os.rmdir('/Users/mac/Desktop/TestDir')
# 拆分路径。最后一部分是最后级别的目录或者文件名(拆分、合并路径并不要求文件或者目录存在,只对字符串进行操作)
>>> os.path.split('/Users/mac/Desktop/TestDir')
('/Users/mac/Desktop', 'TestDir')
# 获取文件扩展名
>>> os.path.splitext('/path/to/1.txt')
('/path/to/1', '.txt')

# 重命名文件
>>> os.rename('test.txt', 'test.py')
# 删除文件:
>>> os.remove('test.py')

序列化

Python中的pickle模块提供对象序列化功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> import pickle
>>> d = dict(name='Tom', age=20)
# pickle.dumps() 方法将对象序列化成 bytes 。然后将这个 bytes 写入文件
>>> pickle.dumps(d)
b'\x80\x03}q\x00(X\x04\x00\x00\x00nameq\x01X\x03\x00\x00\x00Tomq\x02X\x03\x00\x00\x00ageq\x03K\x14u.'

# 或者直接使用 pickle.dump() 直接将对象序列化后写入 file-like Object
>>> f = open('/Users/mac/Desktop/dump.txt', 'wb')
>>> pickle.dump(d, f)
>>> f.close()

# 读取刚才写入的内容,读出文件的内容(bytes),用 pickle.loads() 函数反序列化成对象,也可以用 pickle.load() 方法从 file-like Object 反序列化对象
>>> f = open('/Users/mac/Desktop/dump.txt', 'rb')
>>> pickle.load(f)
{'name': 'Tom', 'age': 20}
>>> f.close()
# 注意:由于不同版本Python不兼容,因此这种方法只能保存不重要的数据。

在序列化 JSON 的时候,我们也可以这样做:

1
2
3
4
5
6
7
8
9
10
# 序列化 JSON
>>> import json
>>> d = dict(name='Tom', age=20)
>>> json.dumps(d)
'{"age": 20, "name": "Tom"}'

# 反序列化
>>> json_str = '{"age": 20, "name": "Tom"}'
>>> json.loads(json_str)
{'age': 20, 'name': 'Tom'}

dump()函数还接受不同的参数,来序列化自定义的对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import json

class Student(object):

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

s = Student('Tom', 20)
# 把任意 class 的实例变为 dict,通常每个 class 实例都有一个 __dict__ 属性,用来存储实例变量。
print(json.dumps(s, default=lambda obj: obj.__dict__))

# 同样也可以通过 loads() 方法将 json 反序列化成对象
def dict2Student(obj):
return Student(d['name'], d['age'])

>>> json_str = '{"age": 20, "name": "Tom"}'
>>> print(json.loads(json_str, object_hook=dict2Student))
<__main__.Student object at 0x10cd3c190>

【译】Python中的metaclass

发表于 2016-12-12 |

译者前言

刚开始学习Python,一直不理解Python中的memetaclass到底是用来做什么的,直到我在stackoverflow上看到这个回答。感觉第二个回答比较好,闲暇时间将它翻译出来作为备用吧。

类对象

在理解metaclass之前,你需要掌握Python中的类。Python中的类借鉴了Smalltalk语言,并且关于类拥有一些奇特的想法。

在大多数语言中,类仅仅是一小段用来描述如何产生对象的代码,这一点在Python中也成立。

1
2
3
4
5
6
7
>>> class ObjectCreator(object):
... pass
...

>>> my_object = ObjectCreator()
>>> print(my_object)
<__main__.ObjectCreator object at 0x8974f2c>

但是,Python中的类不仅仅如此。Python中的类也是对象。

一旦你使用关键字class,Python立即执行并创建一个对象,就像这样在内存中创建一个名字为ObjectCreator的对象

1
2
3
>>> class ObjectCreator(object):
... pass
...

这个对象(类)能创建对象(实例),这就是为什么被称作类。

译者注:这里的类也是对象,而生成的实例也是对象,或者说称之为实例更好些,英文原文如下:

This object (the class) is itself capable of creating objects (the instances), and this is why it’s a class.

它是一个对象,因此:

  • 你可以将它赋值给一个变量
  • 你可以拷贝它
  • 你可以给它添加属性
  • 你可以个哦他传递函数参数

栗子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> print(ObjectCreator) # 你可以打印一个类,因为它是一个对象
<class '__main__.ObjectCreator'>
>>> def echo(o):
... print(o)
...
>>> echo(ObjectCreator) # 你可以把类当做一个参数传递
<class '__main__.ObjectCreator'>
>>> print(hasattr(ObjectCreator, 'new_attribute'))
False
>>> ObjectCreator.new_attribute = 'foo' # 你可以给类添加属性
>>> print(hasattr(ObjectCreator, 'new_attribute'))
True
>>> print(ObjectCreator.new_attribute)
foo
>>> ObjectCreatorMirror = ObjectCreator # 你可把给类赋值给一个变量
>>> print(ObjectCreatorMirror.new_attribute)
foo
>>> print(ObjectCreatorMirror())
<__main__.ObjectCreator object at 0x8997b4c>

动态创建类

由于类是一个对象,你可以在运行时动态创建,就像其他对象那样。首先,你可以使用class在函数中创建一个类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> def choose_class(name):
... if name == 'foo':
... class Foo(object):
... pass
... return Foo # 返回值是一个类,不是一个实例
... else:
... class Bar(object):
... pass
... return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # 函数返回一个类,不是一个实例
<class '__main__.Foo'>
>>> print(MyClass()) # 你可以根据从这个类中创建一个实例
<__main__.Foo object at 0x89c6d4c>

但是这样并不是那么动态,因为你仍然必须写整个类。既然类是对象,那么肯定有东西能生成它。

当你使用class关键字,Python会自动的创建对象。但是像Python中大多数的东西一样,它可以通过手动创建。

还记得函数type吗?这个古老又非常好用的函数可以让你知道某个对象的类型。

1
2
3
4
5
6
7
8
>>> print(type(1))
<type 'int'>
>>> print(type("1"))
<type 'str'>
>>> print(type(ObjectCreator))
<type 'type'>
>>> print(type(ObjectCreator()))
<class '__main__.ObjectCreator'>

type还拥有不同的能力,他也可以在运行时创建一个类。type可以接收一个类描述作为参数,并且返回一个类。(我知道,根据你传入参数的不同,同一个函数拥有两种不同的作用是一件很傻的事情。在Python中这个为了能够向后兼容)

type的用法是这样的:

1
2
3
type(name of the class,
tuple of the parent class (for inheritance, can be empty),
dictionary containing attributes names and values)

栗子:

1
2
>>> class MyShinyClass(object):
... pass

也可以通过这种方法手动创建:

1
2
3
4
5
>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一个类对象
>>> print(MyShinyClass)
<class '__main__.MyShinyClass'>
>>> print(MyShinyClass()) # 创建这个类的实例
<__main__.MyShinyClass object at 0x8997cec>

你会注意到我们使用”MyShinyClass”作为类名,并且作为变量作为这个类的引用。类和变量是不同的,在这里没有必要将它们复杂化。

type接受一个字典来定义类的属性。所以:

1
2
>>> class Foo(object):
... bar = True

也可以写成:

1
>>> Foo = type('Foo', (), {'bar':True})

可以像正常类一样来使用:

1
2
3
4
5
6
7
8
9
>>> print(Foo)
<class '__main__.Foo'>
>>> print(Foo.bar)
True
>>> f = Foo()
>>> print(f)
<__main__.Foo object at 0x8a9b84c>
>>> print(f.bar)
True

当然,你也可以从它继承,因此:

1
2
>>>   class FooChild(Foo):
... pass

或者这样:
译者注:这里要注意type函数的第二个参数要使用单个元素的元组写法。

1
2
3
4
5
>>> FooChild = type('FooChild', (Foo,), {})
>>> print(FooChild)
<class '__main__.FooChild'>
>>> print(FooChild.bar) # bar从Foo继承
True

最终你想要给你的类添加方法,你只要有定义一个函数,并将其赋值给字典中的某个属性即可。

1
2
3
4
5
6
7
8
9
10
11
>>> def echo_bar(self):
... print(self.bar)
...
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})
>>> hasattr(Foo, 'echo_bar')
False
>>> hasattr(FooChild, 'echo_bar')
True
>>> my_foo = FooChild()
>>> my_foo.echo_bar()
True

在动态创建类之后,你可以添加更多的方法,就像向常规的类对象添加方法一样。

1
2
3
4
5
6
>>> def echo_bar_more(self):
... print('yet another method')
...
>>> FooChild.echo_bar_more = echo_bar_more
>>> hasattr(FooChild, 'echo_bar_more')
True

你可以看到,在Python里,类就是对象,你可以在运行时动态的创建类。这就是当你使用关键字class的时候,Python在幕后做的事情,他是通过元类来实现的。

什么是元类 (终于)

元类就是创建类的东西,你定义类是为了创建对象,对吧?同样我们也知道Python中的类就是对象。那么,元类创建类,它们是类的类,你可以将它们当成这样:

1
2
MyClass = MetaClass()
MyObject = MyClass()

你已经看到type让你这样做:

1
MyClass = type('MyClass', (), {})

这是因为函数type是一个元类,它是Python中所有类的元类。现在你可能会疑惑为什么它是小写,而不是Type?

我想它可能是为了与str保持一致,str是创建字符串对象,int是创建整数对象,type就是创建类对象。你可以检查__class__属性。

在Python中一切都是对象,包括整数、字符串、函数、类。它们都是对象,并且所有的这些都可以从类中创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> age = 35
>>> age.__class__
<type 'int'>
>>> name = 'bob'
>>> name.__class__
<type 'str'>
>>> def foo(): pass
>>> foo.__class__
<type 'function'>
>>> class Bar(object): pass
>>> b = Bar()
>>> b.__class__
<class '__main__.Bar'>

那么,__class__的__class__属性是什么?

1
2
3
4
5
6
7
8
>>> age.__class__.__class__
<type 'type'>
>>> name.__class__.__class__
<type 'type'>
>>> foo.__class__.__class__
<type 'type'>
>>> b.__class__.__class__
<type 'type'>

所以说,元类就是创建类对象的东西,你可以叫它“类工厂”。type是Python中的内建元类,当然,你也可以创建自己的元类。

metaclass 属性

当你写一个类的时候,可以添加__metaclass__属性:

1
2
3
class Foo(object):
__metaclass__ = something...
[...]

如果你这样做了,Python将会使用元类来创建类Foo。当心,这里有一些技巧。当你写下class Foo(object),但是类Foo现在还没有在内存当中创建。Python将会在类中查找__metaclass__定义,如果找到了,将会创建类对象Foo,如果没有找到,它将会使用type来创建类。请反复的读这句话。当你写下以下时:

1
2
class Foo(Bar):
pass

Python会按照以下运行:

Foo中有__metaclass__属性吗?如果有,Python通过使用__metaclass__在内存中创建名字为Foo的类对象(我说的是类对象,请跟上我)。如果Python中找不到__metaclass__,它会在模块层级上找__metaclass__,并尝试同样的操作(但只适用于不继承任何东西的类,基本上是旧式的类)。接着,如果Python中没有找到__metaclass__,它会使用Bar(父类)的元类(这里或许默认是type)来穿件类对象。

这里要小心,__metaclass__属性并不是从父类继承而来的,父类中的元类(Bar.__class__)是的。如果Bar使用type()(并不是type.__new__())来创建Bar的__metaclass__属性,子类将不会继承这种行为。

现在有一个很大的问题,__metaclass__中可以存放什么?答案是:可以创建类的东西。那么什么可以创建类呢?type,或者是任何使用到type或者子类化type的东西都可以。

自定义元类

元类的主要目的是当创建类的时候能自动的改变类。通常你会为API做这样的事情,你希望可以创建符合当前上下文的类。

假想已个很傻的例子,当你决定模块中所有的类的属性都应该是大写的形式。有好几种办法可以做到,其中一种是设置模块级别的__metaclass__。幸运的是,__metaclass__能够自动被调用,他并不需要是一个正式的类(我知道某些被称为’class’的东西并不需要是一个class,可以尝试画图理解下,很有帮助)。所以,我们以一个简单的例子作为开始:

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
# 元类将自动通过传给 type 的参数作为自己的参数
def upper_attr(future_class_name, future_class_parents, future_class_attr):
"""
Return a class object, with the list of its attribute turned
into uppercase.
"""

# 选出属性不已 ‘__’ 开都的属性,并把它们变成大写
uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

# 用 type 创建类
return type(future_class_name, future_class_parents, uppercase_attr)

__metaclass__ = upper_attr # 将会影响模块中的所有类

class Foo(): # 全局 __metaclass__ 并不会作用于对象
# 我们也可以定义 __metaclass__ 只作用于这个类和它的子类
bar = 'bip'

print(hasattr(Foo, 'bar'))
# 输出: False
print(hasattr(Foo, 'BAR'))
# 输出: True

f = Foo()
print(f.BAR)
# 输出: 'bip'

现在让我们使用真正的class来作为元类在做一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ‘type’ 实际上是一个类,就像 'str', 'int'
# 所以,你也可以直接从它继承
class UpperAttrMetaclass(type):
# __new__ 是在 __init__ 之前被调用的特殊方法
# __new__ 是用来创建对象,并返回它的方法
# 而 __init__ 只是用来将传入的参数初始化给对象
# 你很少使用到 __new__ ,除非你想控制对象的创建
# 这里创建对象是类,我们想自定义它,所以我们这里重写 __new__
# 你可以在 __init__ 中做你想要做的事情
# 一些高级的用法会涉及到重写 __call__ ,但是我们不用
def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):

uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

return type(future_class_name, future_class_parents, uppercase_attr)

但是这不是真正的面向对象(OOP)。我们直接调用type,并且并没有改写父类的__new__方法,可以这样写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class UpperAttrMetaclass(type):

def __new__(upperattr_metaclass, future_class_name,
future_class_parents, future_class_attr):

uppercase_attr = {}
for name, val in future_class_attr.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

# 重用 type.__new__ 方法
# 这是基本的面向对象,并没有什么魔法
return type.__new__(upperattr_metaclass, future_class_name,
future_class_parents, uppercase_attr)

你或许已经注意到额外的参数upperattr_metaclass,这并没有什么特别的:__new__总是接收它定义的类作为第一个参数。就像你使用self作为一个普通方法或者类的类方法的第一个参数那样。

当然,为了更加清晰,名字我定义的很长。但是就像self那样,所有的参数都有传统的名称,因此在真正的产品中元类应该是这样:

1
2
3
4
5
6
7
8
9
10
11
class UpperAttrMetaclass(type):

def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

return type.__new__(cls, clsname, bases, uppercase_attr)

我们可以使用super来是它更加清晰,这回有利于继承(是的,你可以拥有元类,从元类继承,从type继承):

1
2
3
4
5
6
7
8
9
10
11
class UpperAttrMetaclass(type):

def __new__(cls, clsname, bases, dct):
uppercase_attr = {}
for name, val in dct.items():
if not name.startswith('__'):
uppercase_attr[name.upper()] = val
else:
uppercase_attr[name] = val

return super(UpperAttrMetaclass, cls).__new__(cls, clsname, bases, uppercase_attr)

就是这样,关于元类真的没什么可以说的了。

使用元类的代码比较复杂,其背后原因并不是元类本身。而是你通常使用元类做一些晦涩难懂的事情,依赖自省、控制继承、__dict__等等。

的确,元类在使用黑魔法的时候非常有用,因此会弄出复杂的东西来,但就元类本身,它们是非常简单的:

  • 拦截类的创建
  • 修改类
  • 返回修改的类

为什么使用元类来代替函数?

因为 __metaclass__可以接收任何调用对象,为什么使用类,因为它看起来比较复杂?这里有好几个原因:

  • 意图更加清晰,当你读到UpperAttrMetaclass(type),你会知道接下来做什么。
  • 你可以使用面向对象(OOP),元类可以冲元类中继承二改,重写父类的方法,元类甚至可以使用元类。
  • 你可以使你的代码更加有结构化。当你使用元类的时候像上面的例子那样简单。通常非常的复杂。有能力将多个方法归到一个类当中,对你的代码可读性非常有帮助。
  • 你可以使用 __new__,__init__,__call__方法,这些都允许你做不同的事情。就算你讲所有的东西都放在__new__当中处理,游戏而还是觉得使用__init__更加舒服。
  • 这个东西被称作元类,一定要小心使用。

为什么使用元类?

现在有一个大问题,为什么要使用一些容易出错的功能?好吧,通常你用不到它:

“元类就是深入的魔法,99%的用户应该不要担心。如果你想搞清楚究竟是否需要用到元类,那么你就不需要它。(那些实际用到元类的人都非常清楚地知道他们需要做什么,而且根本不需要解释为什么要用元类。)” —— Python界的领袖 Tim Peters

元类最主要的用途是创建API。一个典型的例子是 Django ORM。它允许你这样定义:

1
2
3
class Person(models.Model):
name = models.CharField(max_length=30)
age = models.IntegerField()

但是,如果你这样定义:

1
2
guy = Person(name='bob', age='35')
print(guy.age)

它不会返回IntegerField对象,它会返回一个int,甚至可以从数据库中取出来。这是可能的,因为models.Model中定义了一个__metaclass__,并使用了一些魔法能够将你定义的Person类转变成对数据库的复杂的hook。Django将这些看起来很复杂的东西通过暴露出一个简单的使用元类的API将其化简,通过这个API重新创建代码,在背后完成真正的工作。

结语

首先,你知道类是一个对象并且能够穿件实例。事实上,类本身也是元类的实例。

1
2
3
>>> class Foo(object): pass
>>> id(Foo)
142630324

Python中一切都是对象,要么是类的实例,要么是元类的实例。

除了type,type实际上是它自己的元类。在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。

其次,元类是复杂的。你或许不想通过使用它来对简单的类做修改,你可以改变类使用下面的两种技巧:

  • monkey patching
  • 类装饰器

99%的时间你需要改变类,你最好使用这些,但是99%的时间你根本不需要修改类。:D


参考

stackoverflow: What is a metaclass in Python

Python中的面向对象

发表于 2016-12-10 |

Python中通过class关键字定义类,object表示从哪个类继承下来的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student(object):
# 通过 __init__ 方法添加初始化变量
def __init__(self, name, age):
self.name = name
self.age = age

def print_age(self):
print('age = ', self.age)

# 提供初始化属性
stu = Student('Tom', 18)
# Tom
stu.name
# age = 18
stu.print_age()

访问权限控制

Python中通过把属性的名称钱加上两个下划线__,这就表明这是一个私有变量,只有内部可以访问。有时候,可能会遇到单下划线开头的实例变量,比如:_name。这样的实例变量外部是可以访问的,但是当我们看到这样的实例变量的时候,要把它看做实例变量,不要随意访问。注意:变量名类似于 __xxx__ 这种变量是特殊变量,可以直接访问,不是私有变量。我们不能用 __name__ 这样的变量名称。

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
class Student(object):
def __init__(self, name, age):
self.__name = name
self.__age = age

def print_age(self):
print_age('%s : %s', self.__name, self.__age)

# 如果想访问私有变量,可以给 __name, __age 添加可读写的接口
def get_name(self):
return self.__name

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

def get_age(self):
return self.__age

def set_age(self, age):
self.__age = age

stu = Student('Tom', 18)

# 私有变量,不能直接从外部访问。
# AttributeError: 'Student' object has no attribute '__name'
stu.__name

外部不能访问__name的原因是:Python解释器将__name变量改成了_Student__name,所以仍然可以通过_Student__name来访问__name变量,不过不推荐这么做。

1
2
# Tom
stu._Student__name

继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Animal(object):
def run(self):
print('Animal is running ...')

class Dog(Animal):
def run(self):
print('Dog is running ...')

# 当子类重写了父类的方法时,总是调用子类的方法。
dog = Dog()
# Dog is running ...
dog.run()

animal = Animal()
animal.run()

多态

由于Dog是从Animal继承而来,所以Dog可以看成为Animal类型,而Animal不能看成Dog。可以用isinstance()来判断变量是否是某个类型:

1
2
3
4
>>> isinstance(animal, Animal)
true
>>> isinstance(animal, Dog)
false

现在给Animal对象增加一个函数,接受一个Animal类型的变量:

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
def run_twice(animal):
animal.run()
animal.run()

# 当传入的对象是`Animal`的实例时,`run_twice()`就打印出:
>>> run_twice(Animal())
Animal is running ...
Animal is running ...

# 当传入的对象是`Dog`的实例时,`run_twice()`就打印出:
>>> run_twice(Dog())
Dog is running ...
Dog is running ...

# 现在增加一个 Cat 类,继承自 Animal
class Cat(Animal):
def run(self):
print('Cat is running ...')

# 当传入 Cat 实例,run_twice() 打印出:
>>> run_twice(Cat())
Cat is running ...
Cat is running ...

# 可以发现,每增加一个 Animal 的子类,不必对 run_twice() 做任何修改,任何依赖 Animal 作为参数的函数或者方法都可以不加修改地正常运行,原因就在于多态。

使用多态的好处:当传入Cat或者Dog时,我们只需要接收Animal类型就可以了。因为 Cat、Dog 都是Animal类型,然后按照Animal类型进行操作,由于Animal类型有run()方法,因此,传入的任意类型,只要是Animal类或者子类,就会自动调用实际类型的run()方法。

对于一个变量,只需要知道是Animal类型,无需确切的知道其子类型,就可以放心的调用run()方法,具体是调用Dog还是Cat类型的run()方法,由运行时该对象的确切类型决定。这就是多态的厉害之处,调用方只管调用,不管细节。当我们新增一种Animal的子类时,只要确保run()方法编写正确,不管原来的代码是如何调用的。这就是著名的开闭原则。

对扩展开放:允许新增Animal子类;
对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

鸭子类型

鸭子类型并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。对于静态语言来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者其子类。对于Python,只要保证传入的对象有一个run()方法就可以了:

1
2
3
def Timer(object):
def run(self):
print('timer is running ...')

获取对象信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 使用 type 判断对象类型
>>> type(123)
<class 'int'>

# 使用 isinstance() 判断对象是否是某种类型,或者是在继承链上
>>> isinstance(dog, Animal)
True

# 有属性'x'吗?
>>> hasattr(obj, 'x')
True

# 设置一个属性'y'
>>> setattr(obj, 'y', 19)

>>> getattr(obj, 'z', 404) # 获取属性'z',如果不存在,返回默认值404
404

# 使用 dir() 获取一个对象的所有属性和方法
>>> dir('123')
['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__iter__', '__le__', '__len__', '__lt__', '__mod__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmod__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'capitalize', 'casefold', 'center', 'count', 'encode', 'endswith', 'expandtabs', 'find', 'format', 'format_map', 'index', 'isalnum', 'isalpha', 'isdecimal', 'isdigit', 'isidentifier', 'islower', 'isnumeric', 'isprintable', 'isspace', 'istitle', 'isupper', 'join', 'ljust', 'lower', 'lstrip', 'maketrans', 'partition', 'replace', 'rfind', 'rindex', 'rjust', 'rpartition', 'rsplit', 'rstrip', 'split', 'splitlines', 'startswith', 'strip', 'swapcase', 'title', 'translate', 'upper', 'zfill']

实例属性、类属性

1
2
3
4
5
6
7
8
9
10
11

class Student(object):
# 增加 name、age 类属性
name = 'Tom'
age = 18
# 通过实例变量,或者 self 变量给实例绑定属性
def __init__(self, name):
self.name = name

s = Student('Bob')
s.score = 90

类属性虽然归类所有,但是类的实例都可以访问到,实例属性千万不能与类属性同名,相同名称的实例属性会屏蔽掉类属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>>> s = Student()
>>> print(s.age) # 打印 name 属性,因为实例没有 age 属性,所以访问类的 age 属性
18
>>> print(Student.age) # 访问类属性
18

s.age = 30 # 绑定实例属性
>>> print(s.age) # 实例属性会屏蔽到类属性
30
>>> print(Student.age) # 类属性并没有被屏蔽,使用 Student.age 仍可以访问
18

>>> del s.age # 删除实例属性 age
>>> print(s.age) # 因为实例没有 age 属性,所以访问类的 age 属性
18

__slots__

使用__slots__可以限定实例的属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Student(object):
# 使用元组来限制class实例能添加的属性。
__slots__ = ('name', 'age')

s = Student()
s.name = 'Tom'
s.age = 18

# 由于 __slots__ 限定类实例属性只能绑定 name 和 age,绑定其他的属性会导致错误
# ERROR: AttributeError: 'Student' object has no attribute 'score'
try:
s.score = 90
except AttributeError as e:
print('AttributeError: ', e)

注意:使用__slots__仅对当前的类实例有效,对其子类是无效的。

__str__

__str__用来做格式化输出:

1
2
3
4
5
6
7
8
9
10
11
class Student(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Student object (name: %s)' % self.name

# 通过变量调用的方式打印对象,是通过调用 __repr__ 函数
__repr__ = __str__

>>> print(Student('Michael'))
Student object (name: Michael)

__iter__

如果一个类想被用于for ... in循环,类似于list那样,必须实现一个 __iter__() 方法,改方法返回一个迭代对象,然后,for循环就会不断的调用该对象的__iter__() 方法获取的循环的下一个值,直到遇到StopIteration错误时退出循环。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Fib(object):
def __init__(self):
self.a, self.b = 0, 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

# 由于Fib实例实现 __iter__ 方法,所以实例可以作用于 for 循环
for n in Fib():
print(n)

__getitem__

如果让一个类像数组那样支持下表运算,需要实现__getitem__()方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Fib(object):
def __getitem__(self, n):
a, b = 1, 1
for x in range(n):
a, b = b, a + b

# 现在可以像数组那样访问任意一项
>>> f = Fib()
>>> f[0]
1
>>> f[1]
1
>>> f[2]
2
>>> f[3]
3
...

__getattr__

当调用类的方法或属性时,如果不存在,就会报AttributeError错误,使用__getattr__(),动态返回一个属性:

1
2
3
4
5
6
7
8
9
10
11
class Student(object):
def __init__(self):
self.name = 'Tom'

def __getattr__(self, attr):
if attr=='age':
return 18

s = Student()
# 如果没有定义 __getattr__ 方法,则会报AttributeError错误
s.age

注意:在没有实现__getattr__方法时,Student类并没有定义age属性,如果访问age属性,会出现AttributeError: 'Student' object has no attribute 'age'错误。如果实现了__getattr__方法,就会动态添加属性。只有没有找到某个属性的情况下,才会调用__getattr__属性。

这里有一个利用 __getattr__设计 REST API 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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__


>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

__call__

在调用实例方法的时候,通常使用instance.method()这种方式调用。然而,也可以通过实现__call__()实现实例本身调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Student(object):
def __init__(self, name):
self.name = name

def __call__(self):
print('self.name = %s', self.name)

# 调用方式,直接调用实例
>>> s = Student('Tom')
>>> s()
self.name = Tom

# 判断变量是函数还是对象可以通过 Callable ,如果对象能被调用,那么这个对象就是 Callable 对象。这样就可以判断一个对象是否是可调用对象。
>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False
>>> callable('str')
False

MixIn

Python是支持多继承的,在不支持多继承的编程语言当中,通常需要设计复杂的继承关系。而Python可以通过MixIn这种方式,为类增加额外的功能。在编写程序的过程中,只要保证主线采取单一继承,通过MixIn来给一个类增加额外的功能。

Enum

Python中也支持Enum类:

1
2
3
4
5
6
7
8
from enum import Enum

Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))

# 这样我们就获得了Month类型的枚举类。可以枚举它的所有成员:
# value 属性是自动赋给成员的 int 常量,默认为1开始
for name, member in Month.__members__.items():
print(name, '=>', member, ',', member.value)

也可以更精确的控制枚举类型,从Enum派生出自定义类。@unique装饰器用来保证没有重复值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from enum import Enum, unique

@unique
class Weekday(Enum):
Sun = 0 # Sun的value被设定为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6

>>> day1 = Weekday.Mon
>>> print(day1)
Weekday.Mon
>>> print(Weekday.Tue)
Weekday.Tue
>>> print(Weekday['Tue'])
Weekday.Tue
>>> print(Weekday.Tue.value)
2
>>> print(day1 == Weekday.Mon)
True


参考

Python官方文档
廖雪峰Python教程

Python中的装饰器

发表于 2016-12-07 |

刚开始学习Python中的装饰器,一直不理解有什么用处,后来在知乎上看到某个回答,解决了自己的一些疑惑,在这里做个记录。

简单的装饰器

Python中的装饰器其实是一个函数,可以在不改变任何的代码的前提下为某个函数增加额外的功能。通常使用装饰器可以抽离出大量与函数功能本身无关的代码并继续重用。也可以理解成为已经存在的对象添加额外的功能。

举个列子,定义个函数:

1
2
3
4
def foo():
print('call foo ...')

foo()

如果现在添加一个新的需求,输出函数执行日志。我们可以定义一个print_log(func)函数:

1
2
3
4
5
6
7
8
9
# print_log函数接收一个函数作为参数,执行完log后再执行真正的业务代码。
def print_log(func):
print("%s is running" % func.__name__)
func()

def foo():
print('call foo ...')

print_log(foo)

这种方式看起来非常好,能满足我们的目的。但是这种方式破坏了原来函数的封装性,以前直接调用函数foo(),现在不得不调用print_log(foo)。当然也可以使用装饰器来达到同样的目的。

1
2
3
4
5
6
7
8
9
10
11
def print_log(func):
def wrapper(*args, **kwargs):
print("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper

def foo():
print('call foo ...')

bar = print_log(foo)
bar()

print_log函数就是装饰器,它将执行的业务方法包再函数里面,我们看起来就好像foo函数被print_log函数装饰了。在函数进入和退出时,被称为一个横切面,这种编程方式被称为面向切片编程。@符号是装饰器的语法糖,在定义函数的时候使用,从而避免再一次赋值操作。

1
2
3
4
5
6
7
8
9
10
11
def print_log(func):
def wrapper(*args, **kwargs):
print("%s is running" % func.__name__)
return func(*args, **kwargs)
return wrapper

@print_log
def foo():
print('call foo ...')

foo()

这样就可以省略前面的foo = print_log(foo),使用@print_log替代。如果还有其他的函数,我们可以继续使用这个装饰器,这样就增加了程序的可读性。

带参数的装饰器

装饰器还可以带参数,比如@decorator(a)。这样就可以提供更大的灵活性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def print_log(level):
def decorator(func):
def wrapper(*args, **kwargs):
if level == "warn":
print("%s is running" % func.__name__)
return func(*args, **kwargs)

return wrapper
return decorator


@print_log(level="warn")
def foo():
print('call foo ...')

foo()

这相当于将原来的装饰器进行一层封装,并返回装饰器decorator,当我们使用@print_log(level="warn")调用的时候,将参数level传递到装饰器当中。

在执行到foo = print_log(level="warn")(foo)时,执行foo.__name__发现该函数变成wrapper,并不是foo。这是因为经过decorator装饰后的函数,返回值已经变成wrapper函数,并不是foo函数。Python内置的functools.wraps能将原函数的信息暂时保存起来。这样使得装饰器函数也有和原函数一样的信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import functools

def print_log(level):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
if level == "warn":
print("%s is running" % func.__name__)
return func(*args, **kwargs)

return wrapper
return decorator

@print_log(level="warn")
def foo():
print('call foo ...')

foo()

内置装饰器

Python中内置很多有用的装饰器: @staticmathod、@classmethod、@property

@property

@property装饰器就是将一个方法变成属性调用,把一个getter方法变成属性,只需要加上@property。此时,@property又创建了另外一个装饰器@score.setter,将一个setter方法变成属性赋值,这样就可以对将方法转换成属性来操作。这样就可以对参数进行检查。举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Student(object):
@property
def score(self):
return self._score

@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError('score must be an integer!')
if value < 0 or value > 100:
raise ValueError('score must between 0 ~ 100')
self._score = value

# 也可以添加只读的属性
@property
def age(self):
return 18

s = Student()
s.score = 60 # 相当于调用 s.set_score(60)
s.score # 相当于调用 s.get_score()
s.score = 1000 # ValueError: score must between 0 ~ 100!

装饰器的顺序:

1
2
3
4
@a
@b
@c
def f ():

等效于:f = a(b(c(f)))

类装饰器

类装饰器比函数装饰器较灵活,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Foo(object):
def __init__(self, func):
self._func = func

def __call__(self):
print ('class decorator runing')
self._func()
print ('class decorator ending')

@Foo
def bar():
print ('bar')

bar()

参考文章

知乎:如何理解Python装饰器

1…3456

JY

52 日志
15 标签
GitHub
© 2019 JY
由 Hexo 强力驱动
|
主题 — NexT.Mist v5.1.4