
教程来自: Intermediate Python
快速浏览一个 Python 进阶的教程,后来发现其实也没有那么进阶。
1. *args 和 **kwargs
比较老套的内容,注意 **kwargs 用法,一种是用来让函数可以接收任意长度 key-value 的变量, 比如:
1 | def test_var_args(f_arg, *argv): |
另一种是使用 unpacking arguments 的特性,来方便向设置了 key-value 变量的函数传入变量,比如:
1 | def test_args_kwargs(arg1, arg2, arg3): |
何时使用他们呢?教程给出两种常用情景:
- Function Decorators: 这个稍后会有介绍
- 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 | import someclass |
2. Debugging
使用 python 自带的 debug 模块 pdb
进行 debug:
1 | $ python -m pdb my_script.py |
这样 pdb
会使你的脚本从第一行开始就停下来,如果你想直接跳到断点处,那么你可以在程序内用 pdb.set_trace()
方法加入断点:
1 | import pdb |
这样你直接运行程序(不需要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 | # generator version |
据说许多在 python2 中返回 list
的情况都被在 python3 中修正成了generator
,因为后者更节约资源。
关于 generator
只能遍历一次的证明(for 循环不会报错因为 for
会自动帮我们 catch 相关 error):1
2
3
4
5
6
7
8
9
10
11
12
13
14
15def 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 | my_string = "Yasoob" |
一个 额外 的补充材料,介绍了 generator expression 和 itertools。
另一个 讲解了 iterator 和 generator 互相的联系。
4. Map, Filter and Reduce
也是比较熟悉的内容,其中 reduce 相对比较少用一点,注意这 map
和filter
返回的是 iterable
对象,没转化成 iterator
前不能直接遍历,别一激动就上 for
循环。
4.1. Map
1 | def multiply(x): |
4.2. Filter
1 | number_list = range(-5, 5) |
filter 作为 builtin 函数效率会比手写循环要高。
4.3. Recude
注意 reduce
要额外import
1 | from functools import reduce |
5. Set Data Structure
很长用的数据结构,用来查重,作者给了个比较少见的例子:
1 | some_list = ['a', 'b', 'c', 'b', 'd', 'm', 'n', 'n'] |
注意 set
是可以直接写 comprehension 的,不知道作者为啥要先包装一个 list。set
还有 difference
, union
和intersection
三个集合操作方法。
6. Ternary Operator
1 | condition_is_true if condition else condition_is_false |
另一种写法,这个没怎么见过:
1 | (if_test_is_false, if_test_is_true)[test] |
注意卸载括号前面的是 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
24def 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
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 | from functools import wraps |
7.2. Decorator 用例
下面作者介绍了一些 Decorator 的用法,你可以用 Decorate 实现对某一类函数的开关控制,注意这个地方使用 *arg
和××kwargs
来把 decorated
中任意形式变量传递给f
:
1 | from functools import wraps |
用 Decorator 来给某个 API 增加权限限制,据说这种方法在 Flash 和 Django 里大量使用:1
2
3
4
5
6
7
8
9
10from functools import wraps
def requires_auth(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 | from functools import wraps |
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
30from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(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
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
# A file called out.log now exists, with the above string
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
21class 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
def myfunc1():
pass
而且你还可以通过继承定义子类:1
2
3
4
5
6
7
8
9
10
11
12
13class 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
10def 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
13foo = ['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
12def 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
5def 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 | # no slots |
据说适当使用 __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
26from 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
9some_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
10from 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
19from 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
10import 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
14from 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
12d = 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
5d = 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
10from 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
6from 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
30from 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
9my_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
4my_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. type
和 id
返回对象的类型和独一无二的 id:1
2
3
4
5
6print(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
6multiples_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 | try: |
或者你可以分开处理:1
2
3
4
5
6
7
8try:
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
9try:
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
15ry:
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 variable和 class variable
这个问题就比较深刻,延伸到了很多之前没见过的内容,需要在这列举一下。
首先 这篇文章 讲的很全面,但是有些不太懂,为了看懂他我又看了如下几个文章:
__new__
和__init__
__getattribute__
和__getatrr__
- @property
- super 的两篇文章:这里 和这里
- descriptor 官方文档
话了一部分时间仍然不是特别懂 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
28class 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
23class 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
。
因为继承了 object
,new 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
17class 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
3data = 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
11import 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 | a_list = [[1, 2], [3, 4], [5, 6]] |
注意 [*e for e in a_list]
这么写是不对的,因为 unpack 不能用在 comprehension 和 lambda 里。
One-line Constructor
1 | class A(object): |
20. for/else
for
其实是可以和 else
搭配的,当 for
循环正常结束,没有遇到 break
的时候,else
内的语句被执行。这有时候可以减少不必要的 flag 变量:1
2
3
4
5
6
7
8for 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
12import 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
6print
# Output:
from __future__ import print_function
print(print)
# Output: <built-in function print>
对于 Python2 和 Python3 不同名的库,可以用如下方法:1
2
3
4try:
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
14def 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
在这里相当于数据的占位符,每次通过 send
向coroutine
里提供数据。
一开始 next
是为了开启 coroutine
,也就是让程序代码运行到yiled
处。
coroutine
应该是并发编程里用到的结构,以后看多线程的时候再来研究。
25. Function caching
Python3.2 以后提供了 decrorator
来缓存函数的返回结果,如果你频繁用到参数相同函数的结果的话,使用 function caching 可以大大节省时间,u 一个例子:1
2
3
4
5
6
7
8
9
10from functools import lru_cache
def fib(n):
if n < 2:
return n
return fib(n-1) + fib(n-2)
for n in range(10)]) print([fib(n)
# 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
18from functools import wraps
def memoize(function):
memo = {}
def wrapper(*args):
if args in memo:
return memo[args]
else:
rv = function(*args)
memo[args] = rv
return rv
return wrapper
def fibonacci(n):
if n < 2: return n
return fibonacci(n - 1) + fibonacci(n - 2)
26. Context Managers
主要就是解释如何写出支持 with
操作的对象,原文 前半部 分比较清楚,最后一部分将 decorator
和generator
结合来实现 context manager
的时候胡说了一堆有的没的最后也没解释原理是什么,我想起来早年看过的 一篇文章 比较详细的介绍了整个 context manager
的内容。
总结
质量很一般的教材,并不很进阶,还有点喜欢吹,感觉想要进一步理解 Python 需要看《Python Cookbook》或者《Fluent PYthon》,但是都是很厚的书,希望能有时间看完。
还没能看到的内容:
- Python 类里的各种 Method
- Python 变量作用域
- Python Regex
希望以后能有时间都弄懂一点。Python 给人的感觉就是很好上手但是很多时候你不知道 what is running under the hood.