Python 学习: Intermediate Python | Touch

Python 学习: Intermediate Python

教程来自: Intermediate Python

快速浏览一个 Python 进阶的教程,后来发现其实也没有那么进阶。

1. *args 和 **kwargs

比较老套的内容,注意 **kwargs 用法,一种是用来让函数可以接收任意长度 key-value 的变量, 比如:

1
2
3
4
5
6
def test_var_args(f_arg, *argv):
print("first normal arg:", f_arg)
for arg in argv:
print("another arg through *argv:", arg)

test_var_args('yasoob', 'python', 'eggs', 'test')

另一种是使用 unpacking arguments 的特性,来方便向设置了 key-value 变量的函数传入变量,比如:

1
2
3
4
5
6
7
8
9
10
def test_args_kwargs(arg1, arg2, arg3):
print("arg1:", arg1)
print("arg2:", arg2)
print("arg3:", arg3)

>>> args = ("two", 3, 5)
>>> test_args_kwargs(*args)
arg1: two
arg2: 3
arg3: 5

何时使用他们呢?教程给出两种常用情景:

  1. Function Decorators: 这个稍后会有介绍
  2. Monkey Patching: 指在 runtime 时候更改一些代码, 举例如下:

Consider that you have a class with a function called get_info which calls an API and returns the response data. If we want to test it we can replace the API call with some test data. For instance:

1
2
3
4
5
6
import someclass

def get_info(self, *args):
return "Test data"

someclass.get_info = get_info

2. Debugging

使用 python 自带的 debug 模块 pdb 进行 debug:

1
$ python -m pdb my_script.py

这样 pdb 会使你的脚本从第一行开始就停下来,如果你想直接跳到断点处,那么你可以在程序内用 pdb.set_trace() 方法加入断点:

1
2
3
4
5
6
7
import pdb

def make_bread():
pdb.set_trace()
return "I don't have time"

print(make_bread())

这样你直接运行程序(不需要python -m pdb)就会自动跳到断点处并开启 debug 模式。

pdb的一些常用命令:

c: continue execution
w: shows the context of the current line it is executing.
a: print the argument list of the current function
s: Execute the current line and stop at the first possible occasion.
n: Continue execution until the next line in the current function is reached or it returns.

其实 s 代表 step,n 代表 next,区别就是 next 不会跳入调用的函数而 s 会。

3. Generator

贼复杂的给出三个概念:

  • Iterable
  • Iterator
  • Iteration

3.1. Iterable

Iterable 对象是含有 __iter__ 或者 __getitem__ 方法的对象。

__iter__方法顾名思义就是用来返回 iterator 的方法,常规操作,所遍历的对象不一定要 indexable(比如dict), 实现一般靠yield

__getitem__是早期 python 在没有成熟的 Iterator 实现方案时候产生的历史遗留方法。该方法实际上是对一个有限的且 indexable 的数据结构(比如 list)进行data[0], data[1], data[2]... 的主次访问来模拟 Iterator 的效果。依靠 IndexError 或者 StopIteration 的报错进行终止。详情见Stack Overflow

3.2. Iterator

Iterator 就是含有 next(python2) 或者 __next__(python3) 方法的对象。也就是那个你实际遍历的东西。

3.3. Iteration

Iteration 就是遍历的过程,或者说循环的过程。他折腾这么多概念在我看来是想要说明 iterator 会给你一个 container 去执行遍历这个过程,但是 iterator 本身不会执行遍历过程。不我知道作者弄这么复杂是为了干什么,突出 generator 的好???

3.4. generator

generator本身就是 iterator,但是你只能遍历generator 一次,因为 generator 是在运行过程中计算出每一步的值的。generator通常以函数形式实现,用 yield 代替return

1
2
3
4
5
6
7
8
9
10
# generator version
def fibon(n):
a = b = 1
for i in range(n):
yield a
a, b = b, a + b

# how to use
for x in fibon(1000000):
print(x)

据说许多在 python2 中返回 list 的情况都被在 python3 中修正成了generator,因为后者更节约资源。

关于 generator 只能遍历一次的证明(for 循环不会报错因为 for 会自动帮我们 catch 相关 error):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def generator_function():
for i in range(3):
yield i

gen = generator_function()
print(next(gen))
# Output: 0
print(next(gen))
# Output: 1
print(next(gen))
# Output: 2
print(next(gen))
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# StopIteration

对于 iterable 对象不是 iterator,从而体现generator 优越感的例子:

1
2
3
4
5
6
7
8
9
10
my_string = "Yasoob"
next(my_string)
# Output: Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: str object is not an iterator

my_string = "Yasoob"
my_iter = iter(my_string)
print(next(my_iter))
# Output: 'Y'

一个 额外 的补充材料,介绍了 generator expression 和 itertools。

另一个 讲解了 iterator 和 generator 互相的联系。

4. Map, Filter and Reduce

也是比较熟悉的内容,其中 reduce 相对比较少用一点,注意这 mapfilter返回的是 iterable 对象,没转化成 iterator 前不能直接遍历,别一激动就上 for 循环。

4.1. Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def multiply(x):
return (x*x)
def add(x):
return (x+x)

funcs = [multiply, add]
for i in range(5):
value = list(map(lambda x: x(i), funcs))
print(value)

# Output:
# [0, 0]
# [1, 2]
# [4, 4]
# [9, 6]
# [16, 8]

4.2. Filter

1
2
3
4
5
number_list = range(-5, 5)
less_than_zero = list(filter(lambda x: x < 0, number_list))
print(less_than_zero)

# Output: [-5, -4, -3, -2, -1]

filter 作为 builtin 函数效率会比手写循环要高。

4.3. Recude

注意 reduce 要额外import

1
2
3
4
from functools import reduce
product = reduce((lambda x, y: x * y), [1, 2, 3, 4])

# Output: 24

5. Set Data Structure

很长用的数据结构,用来查重,作者给了个比较少见的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n']

duplicates = []
for value in some_list:
if some_list.count(value) > 1:
if value not in duplicates:
duplicates.append(value)

print(duplicates)

# use set
some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n']
duplicates = set([x for x in some_list if some_list.count(x) > 1])
print(duplicates)
# Output: set(['b', 'n'])
# Output: ['b', 'n']

注意 set 是可以直接写 comprehension 的,不知道作者为啥要先包装一个 list。
set还有 differenceunionintersection三个集合操作方法。

6. Ternary Operator

1
condition_is_true if condition else condition_is_false

另一种写法,这个没怎么见过:

1
2
3
4
5
6
(if_test_is_false, if_test_is_true)[test]

fat = True
fitness = ("skinny", "fat")[fat]
print("Ali is", fitness)
# Output: Ali is fat

注意卸载括号前面的是 false 时的值,也是奇怪的顺序,这种方式没有 short-circuiting 保障,容易出错不是很提倡使用。

7. Decorator

7.1. Decorator 概念

终于看到点比较想看的内容。Decorator 其实是一种函数,它作用于其他函数,使其他函数功能发生变化。因为 Python 中一皆对象,函数也是对象,所以可以做一些类似 First-class Function 的操作,这是 Decorator 实现的前提。

Decorator 的原理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def a_new_decorator(a_func):

def wrapTheFunction():
print("I am doing some boring work before executing a_func()")

a_func()

print("I am doing some boring work after executing a_func()")

return wrapTheFunction

def a_function_requiring_decoration():
print("I am the function which needs some decoration to remove my foul smell")

a_function_requiring_decoration()
#outputs: "I am the function which needs some decoration to remove my foul smell"

a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)
#now a_function_requiring_decoration is wrapped by wrapTheFunction()

a_function_requiring_decoration()
#outputs:I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()

使用 @来使代码简洁,其实就是语法糖。

1
2
3
4
5
6
7
8
9
10
11
12
13
@a_new_decorator
def a_function_requiring_decoration():
"""Hey you! Decorate me!"""
print("I am the function which needs some decoration to"
"remove my foul smell")

a_function_requiring_decoration()
#outputs: I am doing some boring work before executing a_func()
# I am the function which needs some decoration to remove my foul smell
# I am doing some boring work after executing a_func()

#the @a_new_decorator is just a short way of saying:
a_function_requiring_decoration = a_new_decorator(a_function_requiring_decoration)

但是直接这么写, wrapTheFunction会覆盖掉 a_function_requiring_decoration__name__以及__doc__(docstring),这不是我们想要的。解决办法是使用functools.wraps

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

def a_new_decorator(a_func):
@wraps(a_func)
def wrapTheFunction():
print("I am doing some boring work before executing a_func()")
a_func()
print("I am doing some boring work after executing a_func()")
return wrapTheFunction

@a_new_decorator
def a_function_requiring_decoration():
"""Hey yo! Decorate me!"""
print("I am the function which needs some decoration to"
"remove my foul smell")

print(a_function_requiring_decoration.__name__)
# Output: a_function_requiring_decoration

7.2. Decorator 用例

下面作者介绍了一些 Decorator 的用法,你可以用 Decorate 实现对某一类函数的开关控制,注意这个地方使用 *arg××kwargs来把 decorated 中任意形式变量传递给f

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from functools import wraps
def decorator_name(f):
@wraps(f)
def decorated(*args, **kwargs):
if not can_run:
return "Function will not run"
return f(*args, **kwargs)
return decorated

@decorator_name
def func():
return("Function is running")

can_run = True
print(func())
# Output: Function is running

can_run = False
print(func())
# Output: Function will not run

用 Decorator 来给某个 API 增加权限限制,据说这种方法在 Flash 和 Django 里大量使用:

1
2
3
4
5
6
7
8
9
10
from functools import wraps

def requires_auth(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not check_auth(auth.username, auth.password):
authenticate()
return f(*args, **kwargs)
return decorated

Decorator 另一个常见用法是做 Logging:

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

def logit(func):
@wraps(func)
def with_logging(*args, **kwargs):
print(func.__name__ + "was called")
return func(*args, **kwargs)
return with_logging

@logit
def addition_func(x):
"""Do some math."""
return x + x


result = addition_func(4)
# Output: addition_func was called

7.3. 含有参数的 Decorator

就像 wrap 一样,我们的 Decorator 也可以接受函数参数,但是这里语法有点不太符合预期,你需要把 Decorator 再包装到一个函数里,而不是直接在定义 Decorator 的时候更改参数个数:

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
from functools import wraps

def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + "was called"
print(log_string)
# Open the logfile and append
with open(logfile, 'a') as opened_file:
# Now we log to the specified logfile
opened_file.write(log_string + '\n')
return wrapped_function
return logging_decorator

@logit()
def myfunc1():
pass

myfunc1()
# Output: myfunc1 was called
# A file called out.log now exists, with the above string

@logit(logfile='func2.log')
def myfunc2():
pass

myfunc2()
# Output: myfunc2 was called
# A file called func2.log now exists, with the above string

另外 这里 还有一点其他的用例,可以当作补充来看。

7.4. 用类定义 Decorator

为了代码重用和整洁,Decorator 不仅可以通过函数来定义,还可以通过类来定义:

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

def __call__(self, func):
log_string = func.__name__ + "was called"
print(log_string)
# Open the logfile and append
with open(self.logfile, 'a') as opened_file:
# Now we log to the specified logfile
opened_file.write(log_string + '\n')
# Now, send a notification
self.notify()

def notify(self):
# logit only logs, no more
pass

@logit()
def myfunc1():
pass

而且你还可以通过继承定义子类:

1
2
3
4
5
6
7
8
9
10
11
12
13
class email_logit(logit):
'''
A logit implementation for sending emails to admins
when the function is called.
'''
def __init__(self, email='admin@myproject.com', *args, **kwargs):
self.email = email
super(email_logit, self).__init__(*args, **kwargs)

def notify(self):
# Send an email to self.email
# Will not be implemented here
pass

但是以上两个例子有点误导性,因为你没有在 __call__ 里调用被装饰的函数,我在 这里 这里 看到的例子都更有代表性。

我的理解是当 Decorator 有参数的时候,参数的传递已经让 Decorator 被 call 了一次(函数 Decorator 就直接 call 函数,类定义的 Decorator 就直接调用 __call__ 方法),所以你需要额外再包装一个函数,用来接收被装饰的函数做参数。这就看着很繁琐了。

感觉 Decorator 不亲自去写就永远不是很明了,特别是设计到函数编程的思想,有机会在实际应用的时候再摸索吧。

8. Global & Return

8.1. Global

global的作用就是可以使用或者创建独立于函数之外的全局变量:

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
# first without the global variable
def add(value1, value2):
result = value1 + value2

add(2, 4)
print(result)

# Oh crap, we encountered an exception. Why is it so?
# the python interpreter is telling us that we do not
# have any variable with the name of result. It is so
# because the result variable is only accessible inside
# the function in which it is created if it is not global.
Traceback (most recent call last):
File "", line 1, in
result
NameError: name 'result' is not defined

# Now lets run the same code but after making the result
# variable global
def add(value1, value2):
global result
result = value1 + value2

add(2, 4)
result
6

通常不需要也不建议使用global,有啥事你非要用全局变量啊。

8.2. Return

主要就是想告诉你 Python 的 return 可以返回多值:

1
2
3
4
5
6
7
8
9
10
def profile():
name = "Danny"
age = 30
return name, age

profile_name, profile_age = profile()
print(profile_name)
# Output: Danny
print(profile_age)
# Output: 30

这里提到了 namedtuple,不是很熟悉,详情看 这里

9. Mutation

Python 当中所有 mutable 的类型,全部采用引用传递,也就是说,你赋值以后得到的只是同一个对象的一个别名(或者引用)。当然 immutalbe 的数据类型讨论值传递还是引用传递没啥意义因为你每次想改变他们的值只能重新创造一个新的对象。下面是个 mutable 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
foo = ['hi']
print(foo)
# Output: ['hi']

bar = foo
bar += ['bye']

print(foo)
# Expected Output: ['hi']
# Output: ['hi', 'bye']

print(bar)
# Output: ['hi', 'bye']

如此以来,结合 Python 函数的默认参数,就有一个需要注意的地方:

1
2
3
4
5
6
7
8
9
10
11
12
def add_to(num, target=[]):
target.append(num)
return target

add_to(1)
# Output: [1]

add_to(2)
# Output: [1, 2]

add_to(3)
# Output: [1, 2, 3]

出现这这种情况是因为函数参数的默认值智慧在函数被定义的时候赋值一次(only evaluated when the function is defined),以后每次调用该函数,他的默认参数都是在定义时被赋的值。所以函数的默认参数最好不要有 mutable 类型的数据,否则容易出错。比较提倡的方式是:

1
2
3
4
5
def add_to(element, target=None):
if target is None:
target = []
target.append(element)
return target

这样就可以保证每次调用函数的时候都有一个新的 list 产生。

10. slots Magic:

对于类里的 instance attribute,Python 是通过 dict 来存放的。这样虽然方便使用和随时修改,但是会拖累性能。因为 Python 不能在对象被创建时分配一个固定大小的内存,来存放所有的 attricutes。这样当你有大量‘小对象’时,就会浪费很多内存(我并没有查到这是为什么, 不知道是不是跟 hash 结构需要额外空间来避免频繁碰撞有关,参见Stack Overflow)。

__slots__ 就是用来告诉 Python 不要用 dict 来存放 attributes,而只用固定大小的空间来存放有限的 attributes,从而解决问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# no slots
class MyClass(object):
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
self.set_up()
# ...

# use slots
class MyClass(object):
__slots__ = ['name', 'identifier']
def __init__(self, name, identifier):
self.name = name
self.identifier = identifier
self.set_up()
# ...

据说适当使用 __slots__ 可以减少 40%-50% 内存占用量。PyPy 就默认做了 __slots__ 优化。

11. Virtual Environment

就是讲一下 virtualen 的好处,用来隔离不同 Python 应用的运行环境。

常用命令:

  • virtualenv myproject用来构建虚拟环境。
  • source myproject/bin/activate用来载入虚拟环境。
  • deactivate退出虚拟环境。

    虚拟环境默认隔离全局环境,所以你不能认为全局环境里有的虚拟环境就不用装了。另外有个叫 smartcd 的插件,可以在 cd 入文件目录的时候自动加载虚拟环境,懒一点大家都开心。

    12. Collections

12.1. defaultdict

先看个代码:

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
from collections import defaultdict

colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)

# the tutorial writes 'defaultdict(list)' which is a typo
favourite_colours = defaultdict(colours)

for name, colour in colours:
favourite_colours[name].append(colour)

print(favourite_colours)

# output
# defaultdict(<type 'list'>,
# {'Arham': ['Green'],
# 'Yasoob': ['Yellow', 'Red'],
# 'Ahmed': ['Silver'],
# 'Ali': ['Blue', 'Black']
# })

这就比不允许 duplicate key 的 dict 酷炫多了。

当然 defaultdict 的本职工作是解决 dict 里键值的初始化问题的:

1
2
3
4
5
6
7
8
9
some_dict = {}
some_dict['colours']['favourite'] = "yellow"

import collections
tree = lambda: collections.defaultdict(tree)
some_dict = tree()
some_dict['colours']['favourite'] = "yellow"
# Works fine
# Raises KeyError: 'colours'

这列的 tree 函数实现了 deafultdict 的无限嵌套。一个关于 defaultdict 比较基础的介绍在 这里

12.2. OrderedDict

会保持输入顺序的字典:

1
2
3
4
5
6
7
8
9
10
from collections import OrderedDict

colours = OrderedDict([("Red", 198), ("Green", 170), ("Blue", 160)])
for key, value in colours.items():
print(key, value)
# Output:
# Red 198
# Green 170
# Blue 160
# Insertion order is preserved

12.3. Counter

用来简化计数的:

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

colours = (
('Yasoob', 'Yellow'),
('Ali', 'Blue'),
('Arham', 'Green'),
('Ali', 'Black'),
('Yasoob', 'Red'),
('Ahmed', 'Silver'),
)

favs = Counter(name for name, colour in colours)
print(favs)
# Output: Counter({
# 'Yasoob': 2,
# 'Ali': 2,
# 'Arham': 1,
# 'Ahmed': 1
# })

实际上就是:

1
2
3
4
5
6
7
8
9
10
import collections

print collections.Counter(['a', 'b', 'c', 'a', 'b', 'b'])
print collections.Counter({'a':2, 'b':3, 'c':1})
print collections.Counter(a=2, b=3, c=1)

# Result is:
# Counter({'b': 3, 'a': 2, 'c': 1})
# Counter({'b': 3, 'a': 2, 'c': 1})
# Counter({'b': 3, 'a': 2, 'c': 1})

12.4. Deque:

Python 的双端队列,但是它可以像 list 一样用脚标做索引(效率应该不高吧):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from collections import deque
d = deque()
d.append('1')
d.append('2')
d.append('3')

print(len(d))
# Output: 3

print(d[0])
# Output: '1'

print(d[-1])
# Output: '3'

从两端出队列:

1
2
3
4
5
6
7
8
9
10
11
12
d = deque(range(5))
print(len(d))
# Output: 5

d.popleft()
# Output: 0

d.pop()
# Output: 4

print(d)
# Output: deque([1, 2, 3])

两端进队列:

1
2
3
4
5
d = deque([1,2,3,4,5])
d.extendleft([0])
d.extend([6,7,8])
print(d)
# Output: deque([0, 1, 2, 3, 4, 5, 6, 7, 8])

你还可以限制队列的长度d = deque(maxlen=30)

12.5. namedtuple

感觉在某些情况下是节省内存而替代 dict 的选择,他的本质感觉是给你创造了个对象模板:

1
2
3
4
5
6
7
8
9
10
from collections import namedtuple

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="perry", age=31, type="cat")

print(perry)
# Output: Animal(name='perry', age=31, type='cat')

print(perry.name)
# Output: 'perry'

使用 namedtuple 可以让你的代码易读性提高 (self-documenting),同时有因为是tuple 所以更加轻便。当然你仍然可以使用脚标来做索引得到 namedtuple 里的某个值。

namedtuple可以转成dict

1
2
3
4
5
6
from collections import namedtuple

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type="cat")
print(perry._asdict())
# Output: OrderedDict([('name', 'Perry'), ('age', 31), ...

12.6. Enum

Python 里的枚举类:

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
from collections import namedtuple
from enum import Enum

class Species(Enum):
cat = 1
dog = 2
horse = 3
aardvark = 4
butterfly = 5
owl = 6
platypus = 7
dragon = 8
unicorn = 9
# The list goes on and on...

# But we don't really care about age, so we can use an alias.
kitten = 1
puppy = 2

Animal = namedtuple('Animal', 'name age type')
perry = Animal(name="Perry", age=31, type=Species.cat)
drogon = Animal(name="Drogon", age=4, type=Species.dragon)
tom = Animal(name="Tom", age=75, type=Species.cat)
charlie = Animal(name="Charlie", age=2, type=Species.kitten)

# And now, some tests.
>>> charlie.type == tom.type
True
>>> charlie.type
<Species.cat: 1>

这样‘kitten’和‘cat’实际上就是一个对象了。

13. Enumerate

当你需要脚标而又不想写 while 循环的时候:

1
2
3
4
5
6
7
8
9
my_list = ['apple', 'banana', 'grapes', 'pear']
for c, value in enumerate(my_list, 1):
print(c, value)

# Output:
# 1 apple
# 2 banana
# 3 grapes
# 4 pear

enumrate的第二个参数是指定的启示脚标值,你还可以通过 enumerate 创建list

1
2
3
4
my_list = ['apple', 'banana', 'grapes', 'pear']
counter_list = list(enumerate(my_list, 1))
print(counter_list)
# Output: [(1, 'apple'), (2, 'banana'), (3, 'grapes'), (4, 'pear')]

14. Object introspection

上来就碰见个不认识的单词,’introspect’是说‘内省’,可能来自’inspect’。作者解释说 introspection 就是指在运行阶段 (runtime) 时候决定一个对象类型的能力。大概感觉想是 Java 里的反射吧。

14.1. dir

dir就是用来返回一个对象所含有的 attritues 和 methods,我忘了他是不是会隐藏一些对于你来说不能调用的内容但是原来这就叫’introspection’…主要功能就是帮你回忆你忘掉的变量或方法的名字的。

14.2. typeid

返回对象的类型和独一无二的 id:

1
2
3
4
5
6
print(type(dict))
# Output: <type 'type'>

name = "Yasoob"
print(id(name))
# Output: 139972439030304

14.3. inspect模块

可以在 runtime 阶段获取对象信息的模块,正儿八经用到的应该就是这个模块,然而作者没怎么介绍,用到的时候再看吧。

15. Comprehension

这个是很使用的 Python 独有的特性,简单说 comprehension 就是用来帮助你快素建立 sequence 对系的快捷方法,他有如下四类:

  • list comprehensions
  • dictionary comprehensions
  • set comprehensions
  • generator comprehensions

前三个都差不多,一个简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# list
multiples = [i for i in range(30) if i % 3 == 0]
print(multiples)
# Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]

# dict
mcase = {'a': 10, 'b': 34, 'A': 7, 'Z': 3}

mcase_frequency = {
k.lower(): mcase.get(k.lower(), 0) + mcase.get(k.upper(), 0)
for k in mcase.keys()
}
# mcase_frequency == {'a': 17, 'z': 3, 'b': 34}

# set
squared = {x**2 for x in [1, 1, 2]}
print(squared)
# Output: {1, 4}

‘generator comprehensions’其实就是前面说过’generator expression’,因为他的写法动到了单括号,所以 tuple 没没有 comprehension。

1
2
3
4
5
6
multiples_gen = (i for i in range(30) if i % 3 == 0)
print(multiples_gen)
# Output: <generator object <genexpr> at 0x7fdaa8e407d8>
for x in multiples_gen:
print(x)
# Outputs numbers

16. Exception

16.1. 处理多个 exception:

1
2
3
4
try:
file = open('test.txt', 'rb')
except (IOError, EOFError) as e:
print("An error occurred. {}".format(e.args[-1]))

或者你可以分开处理:

1
2
3
4
5
6
7
8
try:
file = open('test.txt', 'rb')
except EOFError as e:
print("An EOF error occurred.")
raise e
except IOError as e:
print("An error occurred.")
raise e

注意 except 是有顺序的,如果被前置的 except 抓到,程序就不会在检测后之 except 中处理的错误是不是更加符合你的要求了。

16.2. finally

没啥好说的,看个代码吧:

1
2
3
4
5
6
7
8
9
try:
file = open('test.txt', 'rb')
except IOError as e:
print('An IOError occurred. {}'.format(e.args[-1]))
finally:
print("This would be printed whether or not an exception occurred!")

# Output: An IOError occurred. No such file or directory
# This would be printed whether or not an exception occurred!

16.2. try/else

Python 的错误处理里多了个 else,意思是说当 try 内代码没有错误的时候,程序将执行esle,否则不执行else 部分。那为什么需要 else 部分呢?else内的代码直接写道 try 部分内不就好了。原因是当年你把 else 内的代码写道 try 内之后,else 内出现的错误也将被 except 抓到,这可能不是我们需要的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ry:
print('I am sure no exception is going to occur!')
except Exception:
print('exception')
else:
# any code that should only run if no exception occurs in the try,
# but for which exceptions should NOT be caught
print('This would only run if no exception occurs. And an error here'
'would NOT be caught.')
finally:
print('This would be printed in every case.')

# Output: I am sure no exception is going to occur!
# This would only run if no exception occurs. And an error here would NOT be caught
# This would be printed in every case.

else部分的执行顺序在 finally 之前。

17. Class

17.1. instance variableclass variable

这个问题就比较深刻,延伸到了很多之前没见过的内容,需要在这列举一下。
首先 这篇文章 讲的很全面,但是有些不太懂,为了看懂他我又看了如下几个文章:

话了一部分时间仍然不是特别懂 descriptor protocal 以及 Python 底层那些烂七八糟的东西,比较硬核的内容,先留着以后用到再看。

而教程主要给了两个例子,一个是通过 instance 不能修改 class variable 的值,这样只会重新创建一个同名的instance variable

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
class Cal(object):
# pi is a class variable
pi = 3.142

def __init__(self, radius):
# self.radius is an instance variable
self.radius = radius

def area(self):
return self.pi * (self.radius ** 2)

a = Cal(32)
a.area()
# Output: 3217.408
a.pi
# Output: 3.142
a.pi = 43
a.pi
# Output: 43

b = Cal(44)
b.area()
# Output: 6082.912
b.pi
# Output: 3.142
b.pi = 50
b.pi
# Output: 50

另一点就是尽量不要用 mutable 的数据类型做class variable,否则很容易出错:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class SuperClass(object):
superpowers = []

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

def add_superpower(self, power):
self.superpowers.append(power)

foo = SuperClass('foo')
bar = SuperClass('bar')
foo.name
# Output: 'foo'

bar.name
# Output: 'bar'

foo.add_superpower('fly')
bar.superpowers
# Output: ['fly']

foo.superpowers
# Output: ['fly']

17.2. New style class

Python 因为历史遗留问题有两种 class,Python2.1 以前的 old style class 没有集成任何父类(但是我看前面链接里写的是集成子type),而之后的’new style class’均继承自object

因为继承了 objectnew style class 可以使用 __slots__super等一系列方法,Python3 只有new style class,并且即使你不明确写出也会默认继承object

17.3. Magic methods

Python 的类里有一类以‘__’开头结尾的方法,称为 dunder(double underscore)方法,他们相比于普通方法有一写特殊的功能。

__init__

Python 的构造函数,但是 __init__ 其实只是赋值过程,对象创建过程 __new__ 进行的。

__getitem__

用来实现 index 的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class GetTest(object):
def __init__(self):
self.info = {
'name':'Yasoob',
'country':'Pakistan',
'number':12345812
}

def __getitem__(self,i):
return self.info[i]

foo = OldClass()
foo['title']
# Output: 'Yasoob'

foo['number']
# Output: 36845124

18. Lambda

Python 的匿名函数,但是只能有一行,一个实现 parallel sorting 的例子:

1
2
3
data = lsit(zip(list1, list2))
data.sort()
list1, list2 = map(lambda t: list(t), zip(*data))

19. One-liner

有些间断的 Python 命令可以帮你提高生活质量。

Simple Web Server

当你想快速传送文件的时候:

1
2
3
4
5
# Python 2
python -m SimpleHTTPServer

# Python 3
python -m http.server

pprint

用来美化打印出来的含有嵌套结构的数据的:

1
2
3
4
5
6
7
8
9
10
11
import pprint
stuff = ['spam', 'eggs', 'lumberjack', 'knights', 'ni']
stuff.insert(0, stuff[:])
pp = pprint.PrettyPrinter(indent=4)
pp.pprint(stuff)
[['spam', 'eggs', 'lumberjack', 'knights', 'ni'],
'spam',
'eggs',
'lumberjack',
'knights',
'ni']

如果你想打印 json 文件,你可以直接:

1
cat file.json | python -m json.tool

CSV to json

将 CSV 格式文件以 json 形式一件打印出来:

1
python -c "import csv,json;print json.dumps(list(csv.reader(open('csv_file.csv'))))"

Profiling a script

不知道这个是干啥的,大概就是给出程序运行结果加一些 meta 信息。

1
python -m cProfile my_script.py

List Flattening

1
2
3
4
5
6
7
a_list = [[1, 2], [3, 4], [5, 6]]
print(list(itertools.chain.from_iterable(a_list)))
# Output: [1, 2, 3, 4, 5, 6]

# or
print(list(itertools.chain(*a_list)))
# Output: [1, 2, 3, 4, 5, 6]

注意 [*e for e in a_list] 这么写是不对的,因为 unpack 不能用在 comprehension 和 lambda 里。

One-line Constructor

1
2
3
class A(object):
def __init__(self, a, b, c, d, e, f):
self.__dict__.update({k: v for k, v in locals().items() if k != 'self'})

关于 locals
的一些解释。
更多one-lines

20. for/else

for其实是可以和 else 搭配的,当 for 循环正常结束,没有遇到 break 的时候,else内的语句被执行。这有时候可以减少不必要的 flag 变量:

1
2
3
4
5
6
7
8
for item in container:
if search_something(item):
# Found it!
process(item)
break
else:
# Didn't find anything..
not_found_in_container()

21. Python C extension

用 Python 调用 C 的代码,一时半会用不上,跳过了。

22. open

说来说去就是主张用 with 方法打开文件以及注意文件读取时的编码问题。

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

with open('photo.jpg', 'rb') as inf:
jpgdata = inf.read()

if jpgdata.startswith(b'\xff\xd8'):
text = u'This is a JPEG file (%d bytes long)\n'
else:
text = u'This is a random file (%d bytes long)\n'

with io.open('summary.txt', 'w', encoding='utf-8') as outf:
outf.write(text % len(jpgdata))

io,open根据 Stack Overflow 上的描述在 Python3 中就是 open 的别名函数。

23. Targeting Python2+3

主要介绍如何写出兼容 Python2 和 Python3 的程序。比较直接的方法是用__future__,使 Python2 可以使用 Python3 的功能:

1
2
3
4
5
6
print
# Output:

from __future__ import print_function
print(print)
# Output: <built-in function print>

对于 Python2 和 Python3 不同名的库,可以用如下方法:

1
2
3
4
try:
import urllib.request as urllib_request # for Python 3
except ImportError:
import urllib2 as urllib_request # for Python 2

由于 Python2 里面有一些 buildtins 在 Python3 中被移除了,所以为了兼容 Python3 在写 Python2 代码时不要使用,可以用 from future.builtins.disabled import * 来禁用他们。

另外一些外置库还可以给 Python2 提供 Python3 功能,比如:

  • enum pip install enum34
  • singledispatch pip install singledispatch
  • pathlib pip install pathlib

但是如今都 2018 了,谁没事还写 Python2 啊….

24. Coroutines

generator 做对比,generator是数据的生产者,而 coroutine 是数据的消费者,看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def grep(pattern):
print("Searching for", pattern)
while True:
line = (yield)
if pattern in line:
print(line)

search = grep('coroutine')
next(search)
# Output: Searching for coroutine
search.send("I love you")
search.send("Don't you love me?")
search.send("I love coroutines instead!")
# Output: I love coroutines instead!

grep将产生一个 coroutine,而yield 在这里相当于数据的占位符,每次通过 sendcoroutine里提供数据。

一开始 next 是为了开启 coroutine,也就是让程序代码运行到yiled 处。

coroutine应该是并发编程里用到的结构,以后看多线程的时候再来研究。

25. Function caching

Python3.2 以后提供了 decrorator 来缓存函数的返回结果,如果你频繁用到参数相同函数的结果的话,使用 function caching 可以大大节省时间,u 一个例子:

1
2
3
4
5
6
7
8
9
10
from functools import lru_cache

@lru_cache(maxsize=32)
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)

>>> print([fib(n) for n in range(10)])
# Output: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

maxisize用来设置缓存大小,表示最多缓存多少个函数最近返回的结果。你可以使用 fib.cache_clear() 来清空缓存。

对于 Python2 而言你要自己手写类似功能,比如:

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

def memoize(function):
memo = {}
@wraps(function)
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper

@memoize
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)

26. Context Managers

主要就是解释如何写出支持 with 操作的对象,原文 前半部 分比较清楚,最后一部分将 decoratorgenerator结合来实现 context manager 的时候胡说了一堆有的没的最后也没解释原理是什么,我想起来早年看过的 一篇文章 比较详细的介绍了整个 context manager 的内容。

总结

质量很一般的教材,并不很进阶,还有点喜欢吹,感觉想要进一步理解 Python 需要看《Python Cookbook》或者《Fluent PYthon》,但是都是很厚的书,希望能有时间看完。

还没能看到的内容:

  • Python 类里的各种 Method
  • Python 变量作用域
  • Python Regex

希望以后能有时间都弄懂一点。Python 给人的感觉就是很好上手但是很多时候你不知道 what is running under the hood.


This post ends. Thank you for reading!

Title: Python 学习: Intermediate Python

Author: Jason

Created: 2018/06/10 - 12:06

Modified: 2020/02/25 - 18:02

Link: http://jasonhan0929.github.io/Python/Python-learn-intermediate-python/

Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.

Please keep the original link and author name when you repost.

I really appreciate your support!
0%