python基础使用示例
重温一些常用的python
使用基础,示例简单明了。
python
基础
Python
基础主要总结Python
常用内置函数;Python
独有的语法特性、关键词nonlocal
, global
等;内置数据结构包括:列表(list
), 字典(dict
), 集合(set
), 元组(tuple
)
以及相关的高级模块collections
中的Counter
, namedtuple
, defaultdict
,heapq
模块。
求绝对值
1 | abs(-996.25) |
元素都为真
接受一个迭代器,如果迭代器的所有元素都为真,那么返回True,否则返回False
1 | all([1, 3, 5, 0, 8]) |
元素至少一个为真
接受一个迭代器,如果迭代器里至少有一个元素为真,那么返回True,否则返回False
1 | any([0, '', [], ()]) |
进制转换
1 | # 十进制转二进制 |
判断真假
1 | bool([[]]) |
字符串转字节
将一个字符串转换成字节类型
1 | s = 'hello中国world' |
Asall
与十进制转换
1 | # 查看十进制整数对应的ASCII 字符 |
转字典
1 | dict(a='A', b='B') |
查看对象所有方法
不带参数时返回当前范围内的变量、方法和定义的类型列表;带参数时返回参数的属性,
方法列表
1 | class Stu(): |
取商和余数
1 | divmod(86, 15) |
计算表达式
将字符串str
当成有效的表达式来求值并返回计算结果取出字符串中内容
1 | eval('2+5-3*8') |
过滤器
在函数中设定过滤条件,迭代元素,保留返回值为True 的元素
1 | lis = filter(lambda x:x>5, [1, 2, 8, 4, 5, 6, 0]) |
字符串格式化
格式化输出字符串,format(value, formatspec)
实质上是调用了value
的*``format(
formatspec)` 方法。
1 | print('我叫:{0}, 今年:{1}岁'.format('鱼大仙', 16)) |
数字 | 格式 | 输出 | 描述 |
---|---|---|---|
3.1415926 | {:.2f} | 3.14 | 保留小数点后两位 |
3.1415926 | {:+.2f} | +3.14 | 带符号保留小数点后两位 |
-1 | {:+.2f} | -1.00 | 带符号保留小数点后两位 |
2.71828 | {:.0f} | 3 | 不带小数 |
5 | {:0>2d} | 05 | 数字补零 (填充左边, 宽度为2) |
5 | {:x<4d} | 5xxx | 数字补x (填充右边, 宽度为4) |
10 | {:x<4d} | 10xx | 数字补x (填充右边, 宽度为4) |
1000000 | {:,} | 1,000,000 | 以逗号分隔的数字格式 |
0.25 | {:.2%} | 25.00% | 百分比格式 |
1000000000 | {:.2e} | 1.00e+09 | 指数记法 |
13 | {:>10d} | 13 | 右对齐 (默认, 宽度为10) |
13 | {:<10d} | 13 | 左对齐 (宽度为10) |
13 | {:^10d} | 13 | 中间对齐 (宽度为10) |
11 | ‘{:b}’.format(11) ‘{:d}’.format(11) ‘{:o}’.format(11) ‘{:x}’.format(11) ‘{:#x}’.format(11) ‘{:#X}’.format(11) | 1011 11 13 b 0xb 0XB | 进制 |
^, <**, **> 分别是居中、左对齐、右对齐,后面带宽度, : 号后面带填充的字符,只能是一个字符,不指定则默认是用空格填充。
+ 表示在正数前显示 **+**,负数前显示 **-**; (空格)表示在正数前加空格
b、d、o、x 分别是二进制、十进制、八进制、十六进制。
此外我们可以使用大括号 {} 来转义大括号,如下实例:
内存地址
1 | id(obj) |
isinstance
判断object
是否为类classinfo
的实例,是返回true
1 | isinstance(obj, Stu) |
对元素为字典的列表排序
我们很少直接对字典进行排序,而是对元素为字典的列表进行排序。
比如,存在下面的 students 列表,它的元素是字典:
1 | students = [ |
- 按 score 从小到大排序
1 | sorted(students, key=lambda stu: stu['score']) |
需要注意的是,这里是按照字母的 ascii 大小排序的,所以 score 从小到大,即从 ‘A’ 到 ‘C’。
- 按 score 从大到小排序
1 | sorted(students, key=lambda stu: stu['score'], reverse=True) # reverse 参数 |
- 按 score 从小到大,再按 age 从小到大
1 | sorted(students, key=lambda stu: (stu['score'], stu['age'])) |
按 score 从小到大,再按 age 从大到小
像之前一个项目里,知道内置函数可以多元素排序,不知道当时怎么就以为多元素排序方式只能一致,要么都正序要么都倒序,导致那块业务用pandas去解决了多元素排序的问题。
1 | sorted(students, key=lambda stu: (stu['score'], -stu['age'])) |
交集/并集/差集
Python 中的集合也可以看成是数学意义上的无序和无重复元素的集合,因此,我们可以对两个集合作交集、并集等。
看看例子:
1 | 1, 2, 3, 4, 5, 6} s1 = { |
reduce
reduce
函数的使用形式如下:
1 | reduce(function, sequence[, initial]) |
解释:先将 sequence 的前两个 item 传给 function,即 function(item1, item2),函数的返回值和 sequence 的下一个 item 再传给 function,即 function(function(item1, item2), item3),如此迭代,直到 sequence 没有元素,如果有 initial,则作为初始值调用。
也就是说:
1 | reduece(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4) |
看一些例子,就能很快理解了。
1 | lambda x, y: x * y, [1, 2, 3, 4]) # 相当于 ((1 * 2) * 3) * 4 reduce( |
map
map
函数的使用形式如下:
1 | map(function, sequence) |
解释:对 sequence 中的 item 依次执行 function(item),并将结果组成一个 List 返回,也就是:
1 | [function(item1), function(item2), function(item3), ...] |
看一些简单的例子。
1 | def square(x): |
再看一个例子:
1 | def double(x): |
上面的代码中,我们加了 list 转换,是为了兼容 Python3,在 Python2 中 map 直接返回列表,Python3 中返回迭代器。
闭包
在 Python 中,函数也是一个对象。因此,我们在定义函数时,可以再嵌套定义一个函数,并将该嵌套函数返回,比如:
1 | from math import pow |
上面的代码中,函数 make_pow
里面又定义了一个内部函数 inner_func
,然后将该函数返回。因此,我们可以使用 make_pow
来生成另一个函数:
1 | >>> pow2 = make_pow(2) # pow2 是一个函数,参数 2 是一个自由变量 |
我们还注意到,内部函数 inner_func
引用了外部函数 make_pow
的自由变量 n
,这也就意味着,当函数 make_pow
的生命周期结束之后,n
这个变量依然会保存在 inner_func
中,它被 inner_func
所引用。
1 | >>> del make_pow # 删除 make_pow |
像上面这种情况,一个函数返回了一个内部函数,该内部函数引用了外部函数的相关参数和变量,我们把该返回的内部函数称为闭包(Closure)。
在上面的例子中,inner_func
就是一个闭包,它引用了自由变量 n
。
装饰器的副作用
前面提到,使用装饰器有一个瑕疵,就是被装饰的函数,它的函数名称已经不是原来的名称了,回到最开始的例子:
1 | def makeitalic(func): |
函数 hello
被 makeitalic
装饰后,它的函数名称已经改变了:
1 | hello.__name__ |
为了消除这样的副作用,Python 中的 functools 包提供了一个 wraps 的装饰器:
1 | from functools import wraps |
partial 函数
Python 提供了一个 functools 的模块,该模块为高阶函数提供支持,partial 就是其中的一个函数,该函数的形式如下:
1 | functools.partial(func[,*args][, **kwargs]) |
这里先举个例子,看看它是怎么用的。
假设有如下函数:
1 | def multiply(x, y): |
现在,我们想返回某个数的双倍,即:
1 | >>> multiply(3, y=2) |
上面的调用有点繁琐,每次都要传入 y=2
,我们想到可以定义一个新的函数,把 y=2
作为默认值,即:
1 | def double(x, y=2): |
现在,我们可以这样调用了:
1 | >>> double(3) |
事实上,我们可以不用自己定义 double
,利用 partial
,我们可以这样:
1 | from functools import partial |
partial
接收函数 multiply
作为参数,固定 multiply
的参数 y=2
,并返回一个新的函数给 double
,这跟我们自己定义 double
函数的效果是一样的。
所以,简单而言,partial
函数的功能就是:把一个函数的某些参数给固定住,返回一个新的函数。
需要注意的是,我们上面是固定了 multiply
的关键字参数 y=2
,如果直接使用:
1 | double = partial(multiply, 2) |
则 2
是赋给了 multiply
最左边的参数 x
,不信?我们可以验证一下:
1 | from functools import partial |
小结
- partial 的功能:固定函数参数,返回一个新的函数。
- 当函数参数太多,需要固定某些参数时,可以使用
functools.partial
创建一个新的函数。
str & repr
先看一个简单的例子:
1 | class Foo(object): |
在上面,我们使用 print 打印一个实例对象,但如果我们想打印更多信息呢,比如把 name 也打印出来,这时,我们可以在类中加入 __str__
方法,如下:
1 | class Foo(object): |
可以看到,使用 print 和 str 输出的是 __str__
方法返回的内容,但如果直接显示则不是,那能不能修改它的输出呢?当然可以,我们只需在类中加入 __repr__
方法,比如:
1 | class Foo(object): |
可以看到,现在直接使用 Foo('ethan')
也可以显示我们想要的结果了,然而,我们发现上面的代码中,__str__
和 __repr__
方法的代码是一样的,能不能精简一点呢,当然可以,如下:
1 | class Foo(object): |
这样,print打印也会执行__repr__
slots 魔法
在 Python 中,我们在定义类的时候可以定义属性和方法。当我们创建了一个类的实例后,我们还可以给该实例绑定任意新的属性和方法。
看下面一个简单的例子:
1 | class Point(object): |
在上面,我们创建了实例 p 之后,给它绑定了一个新的属性 z,这种动态绑定的功能虽然很有用,但它的代价是消耗了更多的内存。
因此,为了不浪费内存,可以使用 __slots__
来告诉 Python 只给一个固定集合的属性分配空间,对上面的代码做一点改进,如下:
1 | class Point(object): |
上面,我们给 __slots__
设置了一个元组,来限制类能添加的属性。现在,如果我们想绑定一个新的属性,比如 z,就会出错了,如下:
1 | 3, 4) p = Point( |
使用 __slots__
有一点需要注意的是,__slots__
设置的属性仅对当前类有效,对继承的子类不起效,除非子类也定义了 __slots__
,这样,子类允许定义的属性就是自身的 slots 加上父类的 slots。
super 原理
super
的工作原理如下:
1 | def super(cls, inst): |
其中,cls 代表类,inst 代表实例,上面的代码做了两件事:
- 获取 inst 的 MRO 列表
- 查找 cls 在当前 MRO 列表中的 index, 并返回它的下一个类,即 mro[index + 1]
当你使用 super(cls, inst)
时,Python 会在 inst 的 MRO 列表上搜索 cls 的下一个类。
现在,让我们回到前面的例子。
首先看类 C 的 __init__
方法:
1 | super(C, self).__init__() |
这里的 self 是当前 C 的实例,self.class.mro() 结果是:
1 | [__main__.C, __main__.A, __main__.B, __main__.Base, object] |
可以看到,C 的下一个类是 A,于是,跳到了 A 的 __init__
,这时会打印出 enter A,并执行下面一行代码:
1 | super(A, self).__init__() |
注意,这里的 self 也是当前 C 的实例,MRO 列表跟上面是一样的,搜索 A 在 MRO 中的下一个类,发现是 B,于是,跳到了 B 的 __init__
,这时会打印出 enter B,而不是 enter Base。
整个过程还是比较清晰的,关键是要理解 super 的工作方式,而不是想当然地认为 super 调用了父类的方法。
陌生的 metaclass
Python 中的元类(metaclass)是一个深度魔法,平时我们可能比较少接触到元类,本文将通过一些简单的例子来理解这个魔法。
类也是对象
在 Python 中,一切皆对象。字符串,列表,字典,函数是对象,类也是一个对象,因此你可以:
- 把类赋值给一个变量
- 把类作为函数参数进行传递
- 把类作为函数的返回值
- 在运行时动态地创建类
看一个简单的例子:
1 | class Foo(object): |
熟悉又陌生的 type
在日常使用中,我们经常使用 object
来派生一个类,事实上,在这种情况下,Python 解释器会调用 type
来创建类。
这里,出现了 type
,没错,是你知道的 type
,我们经常使用它来判断一个对象的类型,比如:
1 | class Foo(object): |
事实上,type
除了可以返回对象的类型,它还可以被用来动态地创建类(对象)。下面,我们看几个例子,来消化一下这句话。
使用 type
来创建类(对象)的方式如下:
type(类名, 父类的元组(针对继承的情况,可以为空),包含属性和方法的字典(名称和值))
最简单的情况
假设有下面的类:
1 | class Foo(object): |
现在,我们不使用 class
关键字来定义,而使用 type
,如下:
1 | Foo = type('Foo', (object, ), {}) # 使用 type 创建了一个类对象 |
上面两种方式是等价的。我们看到,type
接收三个参数:
- 第 1 个参数是字符串 ‘Foo’,表示类名
- 第 2 个参数是元组 (object, ),表示所有的父类
- 第 3 个参数是字典,这里是一个空字典,表示没有定义属性和方法
在上面,我们使用 type()
创建了一个名为 Foo 的类,然后把它赋给了变量 Foo,我们当然可以把它赋给其他变量,但是,此刻没必要给自己找麻烦。
接着,我们看看使用:
1 | print Foo |
有属性和方法的情况
假设有下面的类:
1 | class Foo(object): |
用 type
来创建这个类,如下:
1 | def greet(self): |
上面两种方式的效果是一样的,看下使用:
1 | f = Foo() |
继承的情况
再来看看继承的情况,假设有如下的父类:
1 | class Base(object): |
我们用 Base 派生一个 Foo 类,如下:
1 | class Foo(Base): |
改用 type
来创建,如下:
1 | Foo = type('Foo', (Base, ), {'foo': True}) |
什么是元类(metaclass)
元类(metaclass)是用来创建类(对象)的可调用对象。这里的可调用对象可以是函数或者类等。但一般情况下,我们使用类作为元类。对于实例对象、类和元类,我们可以用下面的图来描述:
1 | 类是实例对象的模板,元类是类的模板 |
我们在前面使用了 type
来创建类(对象),事实上,**type
就是一个元类**。
那么,元类到底有什么用呢?要你何用…
元类的主要目的是为了控制类的创建行为。我们还是先来看看一些例子,以消化这句话。
元类的使用
先从一个简单的例子开始,假设有下面的类:
1 | class Foo(object): |
现在我们想给这个类的方法和属性名称前面加上 my_
前缀,即 name 变成 my_name,bar 变成 my_bar,另外,我们还想加一个 echo 方法。当然,有很多种做法,这里展示用元类的做法。
1.首先,定义一个元类,按照默认习惯,类名以 Metaclass 结尾,代码如下:
1 | class PrefixMetaclass(type): |
上面的代码有几个需要注意的点:
PrefixMetaClass 从
type
继承,这是因为 PrefixMetaclass 是用来创建类的__new__
__init__1
2
3
4
5
6
7
是在
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
之前被调用的特殊方法,它用来创建对象并返回创建后的对象,对它的参数解释如下:
- cls:当前准备创建的类
- name:类的名字
- bases:类的父类集合
- attrs:类的属性和方法,是一个字典
2.接着,我们需要指示 Foo 使用 PrefixMetaclass 来定制类。
在 Python2 中,我们只需在 Foo 中加一个 `__metaclass__` 的属性,如下:
```python
class Foo(object):
__metaclass__ = PrefixMetaclass
name = 'foo'
def bar(self):
print 'bar'
在 Python3 中,这样做:
1 | class Foo(metaclass=PrefixMetaclass): |
现在,让我们看看使用:
1 | f = Foo() |
可以看到,Foo 原来的属性 name 已经变成了 my_name,而方法 bar 也变成了 my_bar,这就是元类的魔法。
再来看一个继承的例子,下面是完整的代码:
1 | class PrefixMetaclass(type): |
其中,PrefixMetaclass 和 Foo 跟前面的定义是一样的,只是新增了 Bar,它继承自 Foo。先让我们看看使用:
1 | b = Bar() |
我们发现,Bar 没有 prop 这个属性,但是有 my_prop 这个属性,这是为什么呢?
原来,当我们定义 class Bar(Foo)
时,Python 会首先在当前类,即 Bar 中寻找 __metaclass__
,如果没有找到,就会在父类 Foo 中寻找 __metaclass__
,如果找不到,就继续在 Foo 的父类寻找,如此继续下去,如果在任何父类都找不到 __metaclass__
,就会到模块层次中寻找,如果还是找不到,就会用 type 来创建这个类。
这里,我们在 Foo 找到了 __metaclass__
,Python 会使用 PrefixMetaclass 来创建 Bar,也就是说,元类会隐式地继承到子类,虽然没有显示地在子类使用 __metaclass__
,这也解释了为什么 Bar 的 prop 属性被动态修改成了 my_prop。
写到这里,不知道你理解元类了没?希望理解了,如果没理解,就多看几遍吧~
小结
- 在 Python 中,类也是一个对象。
- 类创建实例,元类创建类。
- 元类主要做了三件事:
- 拦截类的创建
- 修改类的定义
- 返回修改后的类
- 当你创建类时,解释器会调用元类来生成它,定义一个继承自 object 的普通类意味着调用 type 来创建它。