一、 函数
1.1 匿名函数(lambda函数)
lambda是一个表达式,用来编写简单的函数,返回一个值。 格式: [ lambda 参数 : 表达式 ]
fun_lambda = lambda x: x ** 2
print(fun_lambda(5)) # 25
1.2 partial函数
函数在执行时要带上必要的参数进行调用。然而有时在函数调用前有部分参数已经提前确定。这种情况下可以定义partial function从而减少函数的参数。
python提供了partial函数用于携带部分参数生成一个新函数,但partial函数不会带来运行效率的提高,只会使代码更简洁。
import functools
def add(a, b):
return a + b
# 定义partial函数plus3
plus3 = functools.partial(add, 3)
print(plus3(7))
1.3 函数参数
函数参数分为位置参数,默认参数,可变参数,关键字参数和命名关键字参数。
1.3.1 位置参数
# a和b是位置参数
def add(a, b):
return a+b
1.3.2 默认参数
注意位置
参数在前,默认参数在后- 注意: 默认参数必须指向不变对象,不能指向list之类的可变对象。eg: def add_end(L=[])是错误的
# name和gender是位置参数,age和city是默认参数
def info(name, gender, age=6, city='Beijing'):
return name,gender,age,city
# 按顺序提供默认参数
print(info('bacon','M',18)) # ('bacon', 'M', 18, 'Beijing')
print(info('bacon','M',18,'Guangzhou')) # ('bacon', 'M', 18, 'Guangzhou')
# 使用参数名方式提供默认方式
print(info('bacon','M',city='Guangzhou',age=18)) # ('bacon', 'M', 18, 'Guangzhou')
# 错误示范
def add_end(L=[]):
L.append('END')
return L
print(add_end()) # ['END']
print(add_end()) # ['END', 'END']
'''
解释:
Python函数在定义的时候,默认参数L的值就被计算出来了,即[],因为默认参数L也是一个变量,
它指向对象[],每次调用该函数,如果改变了L的内容,则下次调用时,默认参数的内容就变了,
不再是函数定义时的[]了。
'''
# 改正
def add_end(L=None):
if L is None:
L = []
L.append('END')
return L
1.3.3 可变参数
可变参数就是传入的参数个数是可变的。可变参数允许你传入0个或任意个参数,这些可变参数在函数调用时自动组装为一个tuple。
def calc(*numbers):
sum = 0
for n in numbers:
sum = sum + n
return sum
print(calc(1, 2)) # 3
print(calc(1, 3, 5, 7)) # 16
print(calc(*[1,2,3,4])) # 10
print(calc(*(1,2,3,4))) # 10
1.3.4 关键字参数
关键字参数允许你传入0个或任意个含参数名的参数,这些关键字参数在函数内部自动组装为一个dict。
def person(name, age, **kw):
if 'city' in kw:
# 有city参数
pass
if 'job' in kw:
# 有job参数
pass
print('name:', name, 'age:', age, 'other:', kw)
person('Michael', 30) # name: Michael age: 30 other: {}
person('Bob', 35, city='Beijing') # name: Bob age: 35 other: {'city': 'Beijing'}
person('Adam', 45, gender='M', job='AI') # name: Adam age: 45 other: {'gender': 'M', 'job': 'AI'}
extra = {'city': 'Beijing', 'job': 'Engineer'}
person('Jack', 24, **extra) # name: Jack age: 24 other: {'city': 'Beijing', 'job': 'Engineer'}
1.3.5 命名关键字参数
- 对于关键字参数,函数的调用者可以传入任意不受限制的关键字参数。至于到底传入了哪些,就需要在函数内部通过
kw
检查。- 如果要限制关键字参数的名字,就可以用命名关键字参数。命名关键字参数需要一个特殊分隔符
*
,*
后面的参数被视为命名关键字参数。命名关键字参数必须传入参数名。- 命名关键字参数可以有缺省值,从而简化调用
# 1
def person(name, age, *, city, job):
print(name, age, city, job)
person('Jack', 24, city='Beijing', job='Engineer') # Jack 24 Beijing Engineer
person('Jack', 24, city='Beijing') # TypeError: person() missing 1 required keyword-only argument: 'job'
# 2 如果函数定义中已经有了一个可变参数,后面跟着的命名关键字参数就不再需要一个特殊分隔符*了
def person(name, age, *args, city='Beijing', job):
print(name, age, args, city, job)
person('Jack', 24,1,2,3, city='Beijing',job='AI') # Jack 24 (1, 2, 3) Beijing AI
1.3.6 函数参数总结
在Python中定义函数,可以用必选参数(位置参数)、默认参数、可变参数、关键字参数和命名关键字参数,这5种参数都可以组合使用。但是请注意,参数定义的顺序必须是:必选参数(位置参数)、默认参数、可变参数、命名关键字参数和关键字参数。
对于任意函数,都可以通过类似
func(*args, **kw)
的形式调用它,无论它的参数是如何定义的。
# 函数参数综合示例
def f1(a, b, c=0, *args, **kw):
print('a =', a, 'b =', b, 'c =', c, 'args =', args, 'kw =', kw)
def f2(a, b, c=0, *, d, **kw):
print('a =', a, 'b =', b, 'c =', c, 'd =', d, 'kw =', kw)
f1(1, 2) # a = 1 b = 2 c = 0 args = () kw = {}
f1(1, 2, c=3) # a = 1 b = 2 c = 3 args = () kw = {}
f1(1, 2, 3, 'a', 'b') # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {}
f1(1, 2, 3, 'a', 'b', x=99) # a = 1 b = 2 c = 3 args = ('a', 'b') kw = {'x': 99}
f2(1, 2, d=99, ext=None) # a = 1 b = 2 c = 0 d = 99 kw = {'ext': None}
args = (1, 2, 3, 4)
kw = {'d': 99, 'x': '#'}
f1(*args, **kw) # a = 1 b = 2 c = 3 args = (4,) kw = {'d': 99, 'x': '#'}
args = (1, 2, 3)
kw = {'d': 88, 'x': '#'}
f2(*args, **kw) # a = 1 b = 2 c = 3 d = 88 kw = {'x': '#'}
二、字符串编解码问题
- 计算机只能处理数字。
- 如果要处理文本,就必须先把文本转换成数字。即 ‘文本’ 编码成 '数字',例如把 'A' 编码成 65.
- 编码表:把文本转换成数字的规则表。例如早期的ASCII编码表编码了127个字符。全世界有上百种语言,中国制定了
GB2312
编码,用来把中文编进去;日本把日文编到Shift_JIS
里;韩国把韩文编到Euc-kr
里;各国有各国的标准,所以会不可避免的出现冲突。在多语言混合的文本中,显示出来会有乱码。- Unicode把所有语言都统一到一套编码里,这样就不会出现乱码问题。常用的是用两个字节表示一个字符(如果要用到非常偏僻的字符,就需要4个字节)。但是,如果你写的文本基本上全部是英文的话,用Unicode编码比ASCII编码需要多一倍的存储空间,在存储和传输上就十分不划算。
- 本着节约的精神,又出现了把Unicode编码转化为“可变长编码”的
UTF-8
编码。UTF-8编码把一个Unicode字符根据不同的数字大小编码成1-6个字节,常用的英文字母被编码成1个字节,汉字通常是3个字节,只有很生僻的字符才会被编码成4-6个字节。如果你要传输的文本包含大量英文字符,用UTF-8编码就能节省空间。UTF-8编码有一个额外的好处,就是ASCII编码实际上可以被看成是UTF-8编码的一部分,所以,大量只支持ASCII编码的历史遗留软件可以在UTF-8编码下继续工作。
字符 | ASCII | Unicode | UTF-8 |
---|---|---|---|
A | 01000001 | 00000000 01000001 | 01000001 |
中 | x | 01001110 00101101 | 11100100 10111000 10101101 |
- 在计算机内存中,统一使用Unicode编码(python的str数据类型,一个字符对应若干个字节,eg: "abc")。
- 保存到硬盘或者需要传输的时候,就转换为UTF-8编码(byte,以字节为单位, eg: b"abc")。
- 例如,用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符加载到内存里(decode: byte -> str);编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件(encode: str -> byte)。
# ord()函数获取字符的整数表示
print(ord('A')) # 65
print(ord("中")) # 20013
# chr()函数把编码转换为对应的字符
print(chr(66)) # B
print(chr(20050)) # 乒
# 编码encode: str -> byte
print('ABC'.encode('ascii')) # b'ABC'
# 在bytes中,无法显示为ASCII字符的字节,用\x##显示。
print('中文'.encode('utf-8')) # b'\xe4\xb8\xad\xe6\x96\x87'
# 解码decode: byte -> str
print(b'ABC'.decode('ascii')) # ABC
print(b'\xe4\xb8\xad\xe6\x96\x87'.decode('utf-8')) # 中文
# 如果bytes中包含无法解码的字节,decode()方法会报错
# 如果bytes中只有一小部分无效的字节,可以传入errors='ignore'忽略错误的字节
print(b'\xe4\xb8\xad\xff'.decode('utf-8', errors='ignore')) # 中
# len()函数计算的是str的字符数,如果换成bytes,len()函数就计算字节数
print(len('ABC')) # 3
print(len(b'ABC')) # 3
print(len('中文')) # 2
print(len('中文'.encode('utf-8'))) # 6 # utf-8一个中文用三个字节编码
- 在操作字符串时,我们经常遇到
str
和bytes
的互相转换。为了避免乱码问题,应当始终坚持使用UTF-8编码对str
和bytes
进行转换。- Python源代码[xxx.py]也是一个文本文件,所以,当你的源代码中包含中文的时候,在保存源代码时,就需要务必指定保存为UTF-8编码。
- 当Python解释器读取源代码时,为了让它按UTF-8编码读取,我们通常在文件开头写上 " # coding = utf8 "
三、 可迭代对象(Iterable),生成器(generator),迭代器(Iterator)
不但可以作用于
for
循环,还可以被next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。
3.1 可迭代对象
- 可以通过
for
循环来遍历list或tuple等,这种遍历称为迭代:Iteration。- 可以直接作用于
for
循环的对象统称为可迭代对象:Iterable
。
d = {'a': 1, 'b': 2, 'c': 3}
for key in d: # 迭代key
for value in d.values() # 迭代value
for k, v in d.items() # 同时迭代key和value
通过collections模块的Iterable类型来判断一个对象是否是可迭代对象
from collections import Iterable
isinstance('abc', Iterable) # True
isinstance([1,2,3], Iterable) # True
isinstance(123, Iterable) # False
如果要对list实现下标循环,Python内置的
enumerate
函数可以把一个list变成索引-元素对,这样就可以在for
循环中同时迭代索引和元素本身。
for i, value in enumerate(['A', 'B', 'C']):
print(i, value)
'''
0 A
1 B
2 C
'''
3.2 生成器
- 生成式是Python内置的非常简单却强大的,可以用来创建相关数据结构的生成式。
- 列表生成器,字典生成器,集合生成器
[x * x for x in range(1, 11) if x % 2 == 0] # [4, 16, 36, 64, 100]
{x: x ** 2 for x in range(1, 5)} # {1: 1, 2: 4, 3: 9, 4: 16}
{x for x in range(1, 5)} # {1, 2, 3, 4}
- 通过列表生成式可以直接创建一个列表。但是如果要创建一个包含100万个元素的列表,受到内存限制,列表容量肯定是有限的。如果列表元素可以按照某种算法推算出来,就不必创建完整的list,从而节省大量的空间。
- 在Python中,这种一边循环一边计算的机制,称为生成器:generator。
- 创建一个generator有很多种方法
1)把一个列表生成式的
[]
改成()
.2)如果一个函数定义中包含
yield
关键字,那么这个函数就不再是一个普通函数,而是一个generator.
# 1)把一个列表生成式的[]改成()
g = (x * x for x in range(3))
# 通过next()函数获得generator的下一个返回值
# 直到计算到最后一个元素,没有更多的元素时,抛出StopIteration的错误。
print(next(g)) # 0
print(next(g)) # 1
print(next(g)) # 4
print(next(g)) # 崩溃,StopIteration
g = (x * x for x in range(3))
# 使用for循环遍历生成器,因为generator也是可迭代对象
for n in g:
print(n)
函数是顺序执行,遇到
return
语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()
的时候执行,遇到yield
语句返回,再次执行时从上次返回的yield
语句处继续执行。
# 2)如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator.
def fib(max):
n, a, b = 0, 0, 1
while n < max:
yield b # 把print改成yield
a, b = b, a + b
n = n + 1
return 'done'
g=fib(5)
for v in g:
print(v) # 1 1 2 3 5
3.3 迭代器
可以被
next()
函数调用并不断返回下一个值的对象称为迭代器:Iterator
。生成器都是Iterator
对象。可以使用isinstance()
判断一个对象是否是Iterator
对象。
list
、dict
、str
虽然是Iterable
,却不是Iterator
。把list
、dict
、str
等Iterable
变成Iterator
可以使用iter()
函数。
from collections import Iterator
isinstance((x for x in range(10)), Iterator) # True
isinstance(iter([]), Iterator) # True
isinstance(iter('abc'), Iterator) # True
四、函数式编程
函数是面向过程的程序设计的基本单元。而函数式编程也可以归结到面向过程的程序设计,但其思想更接近数学计算。函数式编程的一个特点就是,允许把函数本身作为参数传入另一个函数,还允许返回一个函数。
4.1 高阶函数
一个函数接收另一个函数作为参数,这种函数就称之为高阶函数。
map()函数
map()
函数接收两个参数,一个是函数,一个是Iterable
。map
将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator
返回。
def f(x):
return x * x
re = map(f, [1, 2, 3, 4, 5])
print(list(re)) # [1, 4, 9, 16, 25]
reduce()函数
reduce
把一个函数作用在一个序列[x1, x2, x3, ...]
上,这个函数必须接收两个参数,reduce
把结果继续和序列的下一个元素做累积计算。等价于reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)
# 把序列[1, 3, 5, 7, 9]变换成整数13579
from functools import reduce
def fn(x, y):
return x * 10 + y
print(reduce(fn, [1, 3, 5, 7, 9])) # 13579
# 结合map和reduce实现str转int
from functools import reduce
DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
def char2num(s):
return DIGITS[s]
def str2int(s):
return reduce(lambda x, y: x * 10 + y, map(char2num, s))
filter()函数
Python内建的
filter()
函数用于过滤序列。filter()
也接收一个函数和一个序列,把传入的函数依次作用于每个元素,然后根据返回值是True
还是False
决定保留还是丢弃该元素。
# 在一个list中,删掉偶数,只保留奇数
def is_odd(n):
return n % 2 == 1
print(list(filter(is_odd, [1, 2, 4, 5, 6, 9, 10, 15]))) # [1, 5, 9, 15]
# 把一个序列中的空字符串删掉
def not_empty(s):
return s and s.strip()
print(list(filter(not_empty, ['A', '', 'B', None, 'C', ' ']))) # ['A', 'B', 'C']
# 打印1000以内的素数
# 1. 构造一个从3开始的奇数序列。注意这是一个生成器,并且是一个无限序列
def _odd_iter():
n = 1
while True:
n = n + 2
yield n
# 2. 定义一个筛选函数
def _not_divisible(n):
return lambda x: x % n > 0
# 3. 定义一个生成器,不断返回下一个素数
def primes():
yield 2
it = _odd_iter() # 初始序列
while True:
n = next(it) # 返回序列的第一个数
yield n
it = filter(_not_divisible(n), it) # 构造新序列
for n in primes():
if n < 1000:
print(n)
else:
break
sorted函数
Python内置的
sorted()
函数就可以对list进行排序。sorted()
函数也是一个高阶函数,它还可以接收一个key
函数来实现自定义的排序。用
sorted()
排序的关键在于实现一个映射函数。
# 按绝对值大小排序
print(sorted([36, 5, -12, 9, -21], key=abs)) # [5, 9, -12, -21, 36]
'''
keys排序结果 => [5, 9, 12, 21, 36]
| | | | |
最终结果 => [5, 9, -12, -21, 36]
'''
# 小写反向排序
print(sorted(['bob', 'about', 'Zoo', 'Credit'], key=str.lower, reverse=True))
# ['Zoo', 'Credit', 'bob', 'about']
4.2 返回函数
def lazy_sum(*args):
def sum():
ax = 0
for n in args:
ax = ax + n
return ax
return sum
# 当调用lazy_sum()时,返回的并不是求和结果,而是求和函数。
# 当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种程序结构称为“闭包(Closure)”。
lazy_sum(1, 3, 5, 7, 9)() # 25
4.3 装饰器
在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。本质上,decorator就是一个返回函数的高阶函数。
# 定义一个能打印日志的decorator
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)
return func(*args, **kw)
return wrapper
# 把@log放到now()函数的定义处,相当于执行了语句 now = log(now)
@log
def now():
print("now")
now()
'''
call now():
now
'''
由于
log()
是一个decorator,返回一个函数,所以,原来的now()
函数仍然存在,只是现在同名的now
变量指向了新的函数,于是调用now()
将执行新函数,即在log()
函数中返回的wrapper()
函数。
wrapper()
函数的参数定义是(*args, **kw)
,因此,wrapper()
函数可以接受任意参数的调用。在wrapper()
函数内,首先打印日志,再紧接着调用原始函数。在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。decorator可以增强函数的功能,定义起来虽然有点复杂,但使用起来非常灵活和方便。
小知识点汇总
1. 直接运行.py文件
在Mac和Linux上可以像.exe文件那样直接运行.py
文件(在Windows中不行),只需在文件开头定义如下代码。
#!/usr/bin/env python3
2. 输入
name = input("please input name:") # 注意接受的都是str类型
print(name)
3. 动态语言和静态语言
变量本身类型不固定的语言称之为动态语言,与之对应的是静态语言。静态语言在定义变量时必须指定变量类型,如果赋值的时候类型不匹配,就会报错。
a = 'ABC'
b = a
a = 'XYZ'
print(b) # 'ABC'
4. 使用isinstance()进行数据类型检查
# 使用isinstance()进行数据类型检查
def my_abs(x):
if not isinstance(x, (int, float)):
raise TypeError('bad operand type')
if x >= 0:
return x
else:
return -x
my_abs("1") # TypeError: bad operand type
5. 包和模块
- 一个.py文件就称之为一个模块(Module)
- 每个文件夹就是一个包。每一个包目录下面都会有一个
__init__.py
的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。
6. python支持 x+=1,但不支持 x++。
参考