哈
偶尔复习,查漏补缺。(随手写的笔记,不建议看。。)
不可变对象
不可变对象常用在 参数共享/参数传递 上,好处很多,一是可以使用字符串池来节省空间,二是该对象可以安全地共享/传递,不会造成误修改问题。
- numbers
- string
- tuple
- frozenset
a. 问题
在使用*作为重复运算符时,如果目标是一个嵌套的可变对象,就会产生令人费解的问题:
>>> a = [1,2,3]
>>> b = a * 3
>>> b
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> b = [a] * 3 # nested
>>> b
[[1, 2, 3], [1, 2, 3], [1, 2, 3]]
>>> b[1][1] = 4
>>> b
[[1, 4, 3], [1, 4, 3], [1, 4, 3]]
因为 * 并不是深拷贝,它只是简单地复制了 [a] 这个列表,里面的 [1,2,3] 都是同一个对象,所以改了一个,所有的都会改变。
解决方法是不要使用 * 号,改用[a.copy() for i in range(3)]
执行深拷贝。如果不需要修改,请直接使用不可变对象。
b. 奇技淫巧
- 序列分组(使用了浅拷贝的特性)
>>> a = 'asdfghjkl'
>>> iters = [iter(a)] * 3 # 这里是浅拷贝,列表中的三个元素都是同一个迭代器。重点就是这个
>>> parts = zip(*iters) # 参数解包
>>> list(parts)
[('a', 's', 'd'), ('f', 'g', 'h'), ('j', 'k', 'l')]
作用域
- Python 中只有模块,类以及函数才会引入新的作用域,其它的代码块是不会引入新的作用域的。(而在 C/Java 中,任何一个
{}
块就构成一个局部作用域。另外 Julia 中 for/while/try-catch 都是局部作用域,但 if-else 又不是局部作用域。总之这些小差别要注意。) - 局部变量可以与外部变量同名,并且在其作用域中,局部变量会覆盖掉外部变量。
不知是出于实现简单或是性能,还是其他的原因,好像所有的语言都是这样的。其实我更希望变量的作用域覆盖会报错。 - 如果有函数与其他函数或变量(甚至某些保留字)同名,后定义的会覆盖掉先定义的。(这是因为 Python 中函数也是对象。而在 C/Java 中这是会报错的)
此外,还有一个小问题,先看一个例子:
>>> i = 4
>>> def f(): # 单纯的从函数作用域访问外部作用域是没问题的
... print(i)
...
>>> f()
4
再看一个问题举例:
>>> i = 3
>>> def f():
... print(i) # 这里应该是访问外部作用域
... i = 5 # 可这里又定义了一个同名局部变量 i
...
>>> f() # 于是就出错了
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in f
UnboundLocalError: local variable 'i' referenced before assignment
如果在内部作用域先访问外部作用域,再定义一个同名的局部变量,解释器就懵逼了。
如果你其实想做的是改变全局变量 i 的值,就应该在开头声明 global i
. 而如果 外部变量 i 不是存在于全局作用域,而是在某个闭合作用域内的话,就该用 nonlocal i
inspect
P.S. 类比 Java 的反射,但是要简单很多
函数式
- map: Mapping Functions over Iterables
- zip:
- filter: Selecting Items in Iterables
- reduce: Combining Items in Iterables (这个现在在 functools 模块内,常和 operator 模块一起用)
生成器
- yield from: 从另一个生成器返回值
Built-in Functions
时间
- time: 时间模块,用的最多的应该就是 time.time() 了。
- datetime: 时间日期处理模块,支持年月日时分秒的算术运算。
- 性能分析:line_profile: 分析每行的运行时间。
packages
-
__all__
: 用于控制 import * 的行为。 -
通过字符串加载包:importlib
求值
-
compile
将 source 编译成 AST 对象,该对象可传给 eval 或 exec 执行。 -
eval
只接受单个表达式 (expression),evaluate it,并返回表达式的值。 -
exec
接受包含语句 (statement) 的代码段,execute it only for its side effects,无返回值。
装饰器
装饰器有两种:用函数定义的装饰器,还有用类定义的装饰器。函数装饰器最常用。
装饰器可用于装饰函数,修改函数/类的某些行为,或者将函数注册到别的地方。
1. 函数定义装饰器
@decc
def gg(xx):
...
# 等同于
def gg(xx)
gg = decc(gg)
带参的装饰器
@decorator(A, B)
def F(arg):
...
F(99)
# 等同于
def F(arg):
...
F = decorator(A, B)(F) # Rebind F to result of decorator's return value
F(99) # Essentially calls decorator(A, B)(F)(99)
上面演示的是用函数定义的装饰器,也是最常用的装饰器。
装饰器接收的参数可以是各种各样的,下面是一个带参的装饰器:
@on_command("info")
def get_info():
return "这就是你需要的 info"
def on_command(name: str): # 调用此函数获得装饰器,这样就实现了带参装饰器
def deco(func: Callable) -> Callable: # 这个才是真正的装饰器
# 将命令处理器注册到命令列表内
return func # 直接返回原函数,这样的话,多个装饰器就不会相互影响了。
return deco
# 上面的等同于:
get_info = on_command("info")(get_info) # on_command("info") 返回真正的装饰器
如果你的 on_command
有通用的部分,还可以将通用的部分抽离出来复用:
def _deco_maker(event_type: str) -> Callable: # 调用这个,获取 on_xxx 的 deco_deco,
def deco_deco(self) -> Callable: # 这个对应 on_xxx
def deco(func: Callable) -> Callable: # 这个才是真正的装饰器
# do something
return func # 返回原函数
return deco
return deco_deco
我们知道 Python 的类实际上是可以很方便的修改的,因此函数装饰器也能用于装饰类,修改类的某些行为。
def log_getattribute(cls):
# Get the original implementation
orig_getattribute = cls.__getattribute__
# Make a new definition
def new_getattribute(self, name):
print('getting:', name)
return orig_getattribute(self, name)
# Attach to the class and return
cls.__getattribute__ = new_getattribute # 修改了被装饰类 cls 的 __getattribute__
return cls
# Example use
@log_getattribute
class A:
def __init__(self,x):
self.x = x
def spam(self):
pass
2. 类定义装饰器
类定义装饰器和函数定义装饰器的使用方式完全一致。它也可以用于装饰函数或者类。
那么为啥还需要类定义装饰器呢?它的优势在于类是可以继承的,这样的话,就能用继承的方式定义装饰器,将通用部分定义成超类。
类定义装饰器的定义方法如下:
# PythonDecorators/entry_exit_class.py
class entry_exit(object):
def __init__(self, f):
self.f = f
def __call__(self): #关键在于这个函数,它使此类的对象变成 Callable
print("Entering", self.f.__name__)
self.f()
print("Exited", self.f.__name__)
@entry_exit
def func1():
print("inside func1()")
# 上面的装饰器相当于
func1 = entry_exit(func1) # 从这里看的话,装饰器的行为完全一致
# 接下来调用该函数(实际上是调用了 entry_exit 对象的 call 函数)
func1()
输出结果如下:
Entering func1
inside func1()
Exited func1
OOP
-
调用超类方法:
- 直接通过
超类名.__init__(self,xx)
调用 - 通过
super(__class__, self).__init__()
调用。
(Python3 可直接用super().__init__()
但是要搞清楚,super() 方法返回的是一个代理类。另外被代理的类也不一定是其超类。如果不清楚这些差别,最好还是显式用方法一最好。)
- 直接通过
-
抽象超类:@abstractmethod
-
@staticmethod
@classmethod
与 Java 的 static 方法对比
python的类方法、静态方法,与java的静态方法:-
java 中 constants、utils 这样的静态类,对应的是python的一个模块(文件),类属性对应模块的全局属性,静态方法对应模块的函数
-
对于 java 中需要访问类属性的静态方法,如果它不属于第一类,应该用
@classmethod
实现它。classmethod最大的特点就是一定有一个 cls 传入。这种方法的主要用途是实现工厂函数。 -
对于不需要访问任何类属性,也不属于第一类的方法,应该用
@staticmathod
实现。这种方法其实完全不需要放到类里面,它就是一个独立的函数。(仍然放里面,是为了把功能类似的函数组织到一起而已。)
-
-
__slots__
: 属性导出,不在该列表内的属性,若存在则为只读。不存在的话,就不存在。。
6.__getattr__
: 拦截对不存在的属性的访问,可用于实现动态分配属性。 -
__getattribute__
: 和上面相同,但是它拦截对所有属性的访问,包括对已存在的属性的访问。 -
@property: 提供对属性访问的安全检查
-
descriptor: get set delete 控制对类的访问。(上面的 getattr 等是控制对类的属性的访问)
-
类构造器
__new__
:在__init__
之前运行,它接收一个cls
参数,然后使用它构造并返回类实例self
。 -
类方法的
cls
即是当前类,是 type 的实例,cls.xxx
和<类名>.xxx
调用结果是一致的。而 self 由__new__
构造,是 cls 的实例。
元类 metaclasses
元类,也就是用于创建class 的 class,算是很高级的话题了(If you wonder whether you need metaclasses, you don’t )
元类的工作流程:
- 拦截类的创建
- 修改类
- 返回修改之后的类
详细直接看 http://blog.jobbole.com/21351/ 吧。
查看源码
Python 很多功能是 C 写的,直接从 Pycharm 中只能看到自动生成的文档。
对一般的标准库的模块,查看方法是很简单的:直接通过 __file__
属性就能看到。
而对于 builtins 模块,都在这个文件里。