对Element对象的自省

Python中的自省有两个核心模块实现

  • isinstance(obj, cls)
  • issubclass(sub, super)

当判断一个基础数据类型的时候,我们可以这么做

1
2
i = 10
print(isinstance(i, int))

当判断一个自有数据类型的时候,我们可以这么做

1
2
3
4
5
class A:
pass

a = A()
print(isinstance(a, A))

现在我有一个Element对象,如何让他自省呢?

在xml模块中,本身就提供了用于自省的函数

1
2
3
4
5
6
import xml.etree.cElementTree as ET

root = ET.fromstring("""<xml><name>polarsnow</name></xml>""")
print type(root)

print ET.iselement(root)

结果:

1
2
<type 'Element'>
True

Python中的__import__()函数用来动态的加载包文件, 其中__import__()有个fromlist的参数选项, 对于动态加载模块来说, 非常重要

Python 中的 import 大概有以下几种用法

  • import pkg 调用方法: pkg.mod.func()
  • import pkg.mod 调用方法: pkg.mod.func()
  • import mod 调用方法: mod.func()
  • from pkg import mod 调用方法: mod.func()
  • from pkg.mod import func 调用方法 : func()
  • from mod import func 调用方法: func()

第一种和第二种情况下, 当使用__import__导入模块时, 得到的对象是完全一样的. 默认情况下, __import__ 只能导入最左侧的包文件, 即以下两种写法, 导入的对象都是一样的

  • pkg2 = __import__(pkg) 调用方法: pkg2.mod.func()
  • pkg3 = __import__(pkg.mod) 调用方法: pkg3.mod.func()

第三种情况下, 当使用__import__导入模块时, 与import mod的导入效果是一样的

  • mod2 = __import__(mod) 调用方法: mod2.func1(); mod2.func2()

第四种情况下, 当使用__import__导入模块时的用法

  • pkg4 = __import__(pkg, fromlist=[mod1, mod2])

调用方法:

  • pkg4.mod1.func1()
  • pkg4.mod2.func2()

第五种情况下, 不支持使用__import__直接导入包内模块中的函数, 默认情况下, 即使你是如下的写法

  • pkg5 = __import__(pkg.mod)

效果依然是只导入pkg包而已, pkg.mod这个模块并没有被导入

  • pkg5 = __import__(pkg.mod, fromlist=[mod])

如果是加入了 fromlist 关键字的话, 那么该 pkg5 为 mod 模块对象

总结来说就是当 __import__(A.B)时, 当 fromlist 为空, 则导入的对象时 A, 如果 fromlist 不为空, 导入的对象就是 B

第六种情况下, 当使用__import__导入模块时, 与from mod import func的效果是一样的, 不需要使用fromlist参数支持就可以导入全部函数

  • mod3 = __import__(mod) 调用方法: mod3.func()
  • pkg5 = __import__('mod2', fromlist=['func2', 'func3']) 这种写法非常啰嗦, 上面的写法已经可以导入全部函数了, 不需要再使用 fromlist 单独指定

collections.deque 是用来创建一个有序的队列, 而且可以限定其队列的大小, 当队列已经装满后, 再填入的元素将实现头部元素被抛弃, 所有元素向前挪动一位, 新元素追加到最后的效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import collections

q = collections.deque([], 3)

q.append(1)
print(q)

q.append(2)
print(q)

q.append(3)
print(q)

q.append(4)
print(q)

执行结果为:

1
2
3
4
deque([1], maxlen=3)
deque([1, 2], maxlen=3)
deque([1, 2, 3], maxlen=3)
deque([2, 3, 4], maxlen=3)

在 Python 3.5版本(含3.5)之前, 默认的字典是无序的. 在最新的3.6版本中, 字典变成了有序字典, 本篇文章将介绍3.5版本及之前版本中有序字典的使用和3.6版本原生有序字典的使用

3.5-

3.5版本及之前的版本中, 需要借助collections模块中的OrderedDict来实现有序字典的声明和使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from collections import OrderedDict

od = OrderedDict()
d = {}

od['name'] = 'lvrui'
od['age'] = 26
od['city'] = 'HeBei'

d['name'] = 'lvrui'
d['age'] = 26
d['city'] = 'HeBei'

print(od)
print(d)

执行结果为:

1
2
OrderedDict([('name', 'lvrui'), ('age', 26), ('city', 'HeBei')])
{'city': 'HeBei', 'name': 'lvrui', 'age': 26}

3.6+

在 Python3.6+ 版本中, 内置字典默认就是有序的😆 爽~

1
2
3
4
5
6
7
8
9
10
➜  ~ python3.6
Python 3.6.2 (v3.6.2:5fd33b5926, Jul 16 2017, 20:11:06)
[GCC 4.2.1 (Apple Inc. build 5666) (dot 3)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> d = {}
>>> d['name'] = 'lvrui'
>>> d['age'] = 26
>>> d['city'] = 'HeBei'
>>> d
{'name': 'lvrui', 'age': 26, 'city': 'HeBei'}

元组是一种高效的数据类型, 同时我们为了提高提取元组元素的可读性, 做出如下修改来为元组的元素命名

比如我们用一个元组来描述一个位置信息, 比如 (5,9)来标识横坐标信息和纵坐标信息

第一种方式: 通过变量

1
2
3
4
p = (5, 9)
x, y = range(2)
print(p[x])
print(p[y])

执行结果为:

1
2
5
9

第二种方式: collections 内置函数

1
2
3
4
5
import collections
P = collections.namedtuple('P', ['x', 'y'])
p = P(5, 9)
print(p.x)
print(p.y)

执行结果为:

1
2
5
9

查看p的数据类型

1
2
print(isinstance(p, P))
print(isinstance(p, tuple))

执行结果为:

1
2
True
True

在 Python 中可以使用 collections 内置模块下的 Counter 来实现简单的统计功能

统计列表中的元素

1
2
3
4
5
6
7
8
import collections
import random

l = [chr(random.randint(97, 102)) for i in range(20)]
print(l)

result = collections.Counter(l)
print(result)

执行结果为:

1
2
['f', 'f', 'b', 'e', 'e', 'c', 'd', 'f', 'b', 'f', 'c', 'd', 'c', 'f', 'e', 'c', 'e', 'f', 'e', 'e']
Counter({'f': 6, 'e': 6, 'c': 4, 'd': 2, 'b': 2})

统计文件中单词出现的频率

1
2
3
4
5
6
7
8
import collections
import re
f = open('/etc/apache2/extra/httpd-ssl.conf', 'r')

txt = re.split('\W+', f.read())
s = collections.Counter(txt)

print(s)

执行结果为:

1
2
Counter({'the': 93, 'to': 39, 'and': 32, 'for': 31, 'SSL': 29, 'is': 26, 'a': 21, 'of': 21, 'private': 20, 'in': 18, 'you': 17, 'server': 17, 'file': 16, 'apache2': 16, 'be': 16, '1': 16, 'etc': 15, 'certificate': 15, 'this': 14, 'as': 13, 'This': 13, '0': 13, 'or': 13, 'use': 12, 'client': 10, 'that': 10, 'when': 10, 'can': 10, 'are': 9, 'mod_ssl': 9, 'ciphers': 9, 'CA': 8, 'on': 8, 'one': 8, '2': 8, 'an': 7, 'crt': 7, 'standard': 7, 'TLS': 7, 'The': 7, 'directives': 7, 'ssl': 7, 'Use': 7, 'only': 7, 'o': 7, 'shutdown': 7, 'dev': 7, 'all': 6, 'more': 6, 'close': 6, 'used': 6, 'not': 6, 'which': 6, 'notify': 6, 'key': 6, 'default': 6, 'certificates': 6, 'alert': 6, 'var': 6, 'also': 6, 'should': 5, 'OpenSSL': 5, 'SSLv3': 5, 'PEM': 5, 'encoded': 5, 'clients': 5, 'random': 5, 'OCSP': 5, 'containing': 5, 'with': 5, 'connection': 5, 'from': 5, 'SSLCertificateFile': 5, 'user': 5, 'both': 5, 'parallel': 4, 'variables': 4, 'usually': 4, 'at': 4, 'Note': 4, 'HIGH': 4, 'configured': 4, 
......

以上方法在读取文件的时候, 采用了读取全部内容到内存的方法, 如果文件体积较大, 会对内存造成较大压力, 可以通过如下这种迭代的用法实现相同的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import collections
import re
f = open('/etc/apache2/extra/httpd-ssl.conf', 'r')

# 创建一个空集合对象
s = collections.Counter()

# 迭代文件中的每一行
for line in f:
# 将每行内容进行按单词的切割, 在切割之前去掉每行字符串的首位空格以及换行
res = re.split('\W+', line.strip())

# 字符串切割后, 拿到 res 列表, 循环迭代它
for word in res:
# 如果不是空字符的话就在集合中将该键出现的次数+1
if word:
s[word] += 1

print(s)

执行结果与上面相同

统计后找出前三个元素

在 counter 对象中, 还有一个功能可以帮我们取出指定的元素

1
2
3
4
5
6
7
8
9
10
11
12
import collections
import re
f = open('/etc/apache2/extra/httpd-ssl.conf', 'r')

s = collections.Counter()
for line in f:
res = re.split('\W+', line.strip())
for word in res:
if word:
s[word] += 1

print(s.most_common(3))

执行结果为:

1
[('the', 93), ('to', 39), ('and', 32)]

在 Python 中, 提供给我们了两种排序的方式, 一种是列表对象自己的.sort方法, 一种是Python 内置的sorted函数

列表排序

列表排序有两种方式可以实现, 分别是自带的 .sort方法, 还有内置的sorted函数

1
2
3
4
import random

l = [random.randint(1,100) for i in range(10)]
print(l)

执行结果为:

1
[16, 42, 84, 78, 22, 50, 86, 14, 95, 75]

.sort 方法

列表对象自身的.sort方法执行后, 会改变原列表对象的顺序

1
2
l.sort()
print(l)

执行结果为:

1
[14, 16, 22, 42, 50, 75, 78, 84, 86, 95]

通过上面的结果可以看出, 原列表的成员位置已经发生了变化

sorted 函数

使用sorted函数排序后会返回新的对象, 不会在原对象中修改

1
2
l2 = sorted(l)
print(l2)

执行结果为:

1
[14, 16, 22, 42, 50, 75, 78, 84, 86, 95]

元组排序

元组中的元素是有序的, 但是本身的元素是不可修改的, 所以元组自身没有.sort方法可以使用. 但是可以通过转化为列表的方式, 间接使用.sort

使用类型转换得到元组

1
2
3
4
5
6
7
8
import random

l = [random.randint(1,100) for i in range(10)]
t = tuple(l)
print(t)

t2 = sorted(t)
print(t2)

执行结果为:

1
2
(10, 47, 33, 68, 5, 86, 95, 51, 12, 28)
[5, 10, 12, 28, 33, 47, 51, 68, 86, 95]

使用生成器表达式得到元组(迭代对象)

1
2
3
4
5
t = (random.randint(1,100) for i in range(10))
print(t)

t2 = sorted(t)
print(t2)

执行结果为:

1
2
<generator object <genexpr> at 0x1019962b0>
[6, 40, 46, 58, 68, 72, 88, 89, 93, 97]

集合排序

集合本身是无序的, 本身也没有.sort方法. 但是集合也可以间接的通过先转化为列表的方式来使用.sort方法

1
2
3
4
5
s = {random.randint(1,100) for i in range(10)}
print(s)

s2 = sorted(s)
print(s2)

执行结果为:

1
2
{100, 6, 71, 11, 50, 21, 62, 89, 26, 94}
[6, 11, 21, 26, 50, 62, 71, 89, 94, 100]

字典排序

依据 key 排序

使用 sorted 对一个字典进行排序, 只需要把对象传进去即可

1
2
3
4
d = {chr(random.randint(97, 122)):random.randint(1,20) for i in range(1, 11)}
print(d)
d2 = sorted(d)
print(d2)

执行结果为:

1
2
{'m': 11, 's': 18, 'e': 1, 'n': 15, 'v': 17, 'a': 4, 'h': 6, 'i': 20, 'l': 10}
['a', 'e', 'h', 'i', 'l', 'm', 'n', 's', 'v']

当我们拿到这个排序过后的列表之后, 就可以依据列表里的”键”的顺序, 到对应的字典中取值了

依据 value 排序

依据 value 排序就需要用到 sorted 函数中的key参数了, key参数接收一个函数, 此函数即为处理迭代对象每一个元素的函数

1
2
3
4
5
6
d = {chr(random.randint(97, 122)):random.randint(1, 20) for i in range(1, 11)}
print(d)
# 字典对象.items() 会把每个键值对转化成元组, 并把字典的最外层转化为列表
print(d.items())
d2 = sorted(d.items(), key=lambda x:x[1])
print(d2)

执行结果为:

1
2
3
{'y': 8, 'h': 4, 'd': 17, 'p': 9, 'x': 10, 'o': 14, 'c': 15, 'a': 9, 'z': 9}
dict_items([('y', 8), ('h', 4), ('d', 17), ('p', 9), ('x', 10), ('o', 14), ('c', 15), ('a', 9), ('z', 9)])
[('h', 4), ('y', 8), ('p', 9), ('a', 9), ('z', 9), ('x', 10), ('o', 14), ('c', 15), ('d', 17)]

从结果的第一行和第二行可以看出字典. items()的执行效果.

key 后面的 lambda 函数, 将循环接收前面迭代元素的每一个值, 第一次迭代时, 第一个值 ('y', 8)被传递给 lambda 函数的参数. lambda 函数接收这个参数, 并取这个参数值的第二位值, 也就是取得 ('y', 8)的第二个值, 并依据此值进行排序

列表嵌套字典混合排序

1
2
3
4
5
6
7
8
dd = [
{'name': 'lvrui', 'age': 27},
{'name': 'yyy', 'age': 26},
{'name': 'ps', 'age':28}
]
print(dd)
dd.sort(key=lambda x: x['age'])
print(dd)

以上的方式是通过列表自身的.sort方法实现的, 修改了元列表内部元素的位置

1
2
3
4
5
6
7
8
9
dd = [
{'name': 'lvrui', 'age': 27},
{'name': 'yyy', 'age': 26},
{'name': 'ps', 'age':28}
]
print(dd)

dd2 = sorted(dd, key=lambda x: x['age'])
print(dd2)

以上则是通过sorted函数实现.

列举这种用法主要是为了体现 sorted 函数中, key 参数的使用方法, 前面的迭代对象的中每个值, 都会依次传递给 key 中的函数, key 中的函数需要接收一个参数, 并对这个参数的值进行处理, 返回需要排序的对象

首选创建一个集合

1
2
3
4
5
6
import random
import time

l = [random.randint(-10, 10) for i in range(20)]
s = set(l)
print(s)

执行结果为:

1
{2, 3, 4, 5, 7, 8, 10, -1, -9, -6, -5, -4, -2}

现有一个需求, 需要此集合中筛选出可以被3整除的数字都有哪些, 以下给出四种实现方式

第一种方式: 循环迭代

1
2
3
4
5
6
7
print(time.time())
s1 = set()
for item in s:
if item % 3 == 0:
s1.add(item)
print(time.time())
print(s1)

执行结果为:

1
2
3
1500954512.77478
1500954512.774822
{-6, 3, -9}

得到结果需要的时间为: 0.000042

第二种方式: filter 函数

1
2
3
4
5
print('---')
print(time.time())
s2 = list(filter(lambda x: x % 3 == 0, s))
print(time.time())
print(s2)

执行结果为:

1
2
3
1500954512.774839
1500954512.774854
[3, -9, -6]

得到结果需要的时间为: 0.000015

第三种方式: 集合解析式

1
2
3
4
print(time.time())
s3 = {i for i in s if i % 3 == 0}
print(time.time())
print(s3)

执行结果为:

1
2
3
1500954512.774865
1500954512.774885
{-6, 3, -9}

得到结果需要的时间为: 0.000020

第四种方式: 生成器表达式

1
2
3
4
5
print(time.time())
s4 = (i for i in s if i % 3 == 0)
print(time.time())
for i in s4:
print(i)

执行结果为:

1
2
3
4
5
1500954512.774917
1500954512.774934
3
-9
-6

总结:

集合与之前的字典,列表一样, 要实现数据的筛选依然有四种选择, 小数据量下依然推荐使用集合解析式, 大数据量的情况下推荐使用生成器表达式filter 函数(直接返回 filter 迭代对象, 而不直接使用 list 转换)

首先创建一个字典

1
2
3
4
5
import random
import time

d = {str(x): random.randint(60, 100) for x in range(1, 21)}
print(d)

执行结果为:

1
{'16': 61, '3': 66, '17': 70, '10': 82, '1': 71, '18': 89, '5': 83, '4': 97, '19': 77, '12': 92, '8': 73, '11': 74, '15': 60, '13': 68, '20': 80, '7': 62, '2': 99, '6': 85, '14': 83, '9': 87}

现有一个需求, 需要此列表中筛选出值大于90的 id(key) 都有哪些, 以下给出四种实现方式

第一种方法: 循环迭代

1
2
3
4
5
6
7
print(time.time())
d2 = {}
for k in d:
if d[k] > 90:
d2[k] = d[k]
print(time.time())
print(d2)

执行结果为:

1
2
3
1500951937.126503
1500951937.126532
{'12': 92, '2': 99, '4': 97}

得到结果需要的时间为: 0.000029

第二种方法: filter 函数

1
2
3
4
print(time.time())
d5 = list(filter(lambda x: d[x] > 90, d))
print(time.time())
print(d5)

执行结果为:

1
2
3
1500951937.126604
1500951937.126618
['4', '12', '2']

得到结果需要的时间为: 0.000014

第三种方法: 字典解析式

1
2
3
4
print(time.time())
d3 = {k:v for k,v in d.items() if v > 90}
print(time.time())
print(d3)

执行结果为:

1
2
3
1500951937.126555
1500951937.126564
{'12': 92, '2': 99, '4': 97}

得到结果需要的时间为: 0.000009

第四种方法: 生成器表达式

1
2
3
4
5
print(time.time())
d4 = ({k: v} for k, v in d.items() if v > 90)
print(time.time())
for i in d4:
print(i)

执行结果为:

1
2
3
4
5
1500951937.126576
1500951937.126581
{'4': 97}
{'12': 92}
{'2': 99}

得到结果需要的时间为: 0.000005

总结:

在字典数据量较小时, 依然推荐使用字典解析式去筛选数据, 在数据量较大且数据需要一条一条处理时, 生成器依然是最佳的选择

首先我们创建一个列表

1
2
3
4
5
import random
import time

l = [random.randint(-10, 10) for i in range(10)]
print(l)

执行结果为:

1
[-1, 0, 8, 3, 5, 0, 4, 0, -2, 5]

现有一个需求, 需要在这个列表中筛选出大于0的数字, 以下给出四种实现方式

第一种方法: 循环迭代

1
2
3
4
5
6
7
l2 = []
print(time.time())
for item in l:
if item > 0:
l2.append(item)
print(time.time())
print(l2)

执行结果为:

1
2
3
1500915233.146998
1500915233.147016
[8, 3, 5, 4, 5]

得到结果需要的时间为0.000018

第二种方法: filter函数

1
2
3
4
print(time.time())
l3 = list(filter(lambda x: x>0, l))
print(time.time())
print(l3)

执行结果为:

1
2
3
1500915233.147029
1500915233.147039
[8, 3, 5, 4, 5]

得到结果需要的时间为0.000010

第三种方法: 列表解析式

1
2
3
4
print(time.time())
l4 = [item for item in l if item > 0]
print(time.time())
print(l4)

执行结果为:

1
2
3
1500915233.14705
1500915233.147056
[8, 3, 5, 4, 5]

得到结果需要的时间为0.000006

第四种方法: 生成器表达式

如果列表数据量巨大, 应该考虑的不是使用列表解析器, 而是生成器表达式

1
2
3
4
print(time.time())
l5 = (item for item in l if item > 0)
print(time.time())
print(type(l5))

执行结果为:

1
2
3
1500916299.74144
1500916299.741445
<class 'generator'>

得到结果需要的时间为0.000005

总结:

通过以上的四种实践方式得出, 使用循环迭代的方式性能最低, 小数据量时, 使用列表解析式是最好的选择; 如果数据量巨大, 应该使用生成器表达式生成一个可迭代对象提供使用

备注:

  • 列表解析 [expr for iter_var in iterable if cond_expr]

  • 生成器表达式 (expr for iter_var in iterable if cond_expr)

两者的语法非常相似, 但生成器表达式返回的不是一个列表类型对象, 而是一个生成器对象, 生成器是一个内存使用友好的结构