Python—基础(2)

137 阅读14分钟

基本特性

Slice 切片

listtuple的部分元素

L = ['Michael', 'Sarah', 'Tracy', 'Bob', 'Jack']
print(L[0:3])
# 第一个索引为0 则可以省略
print(L[:3])
# 获取倒数第一个元素
print(L[-1:])

slice 简单操作

l1 = list(range(100))
# 获取前10个数
print(l1[:10])
# 获取后10个数
print(l1[-10:])
# 获取10到19之间的数
print(l1[10:20])
# 前10个数,每两个取一个
print(l1[:10:2])
# 所有的数,每5个获取一个
print(l1[::5])
# 原样复制一个list
print(l1[:])

tuple 也是一种list,也可以使用slice结果依然是tuple

t1 = tuple(range(100))
print(t1[::5])

字符串也是一种list, 也可以用slice结果仍是字符串

s1 = 'hello world'
print(s1[:3])
print(s1[::2])

list Comprehensions 列表生成式

列表生成式List Comprehensions,是Python内置的非常简单却强大的可以用来创建 list 的生成式

使用range生成list

print(list(range(1, 11)))

使用for循环生成列表

L = []
for x in range(1, 11):
    L.append(x * x)
print(L)

使用list comprehensions 生成列表

L = [x * x for x in range(1, 11)]
print(L)

for 后可跟条件来过滤元素(if是一个筛选条件,不能带else)

L = [x * x for x in range(1, 11) if x % 2 == 0]
print(L)

for 前面是一个表达式可使用if-else

L = [x if x % 2 == 0 else -x for x in range(1, 11)]
print(L)

可以使用两层循环,可以生成全排列

L = [x + y for x in 'ABC' for y in 'abc']
print(L)

列出当前目录下的所有文件和目录名

d = [d for d in os.listdir('.')]
print(d)

列表生成式可以指定多个生成参数

d = {'x': 'A', 'y': 'B', 'z': 'C'}
L = [k + '=' + v for k, v in d.items()]
print(L)

把所有元素都变成小写

L = ['Hello', 'World', 'IBM', 'Apple']
print([item.lower() for item in L])

generator 列表生成器

创建 generator:[] 改为 ()

创建的g是一个对象

g = (x * x for x in range(10))
print(g)
print(next(g))
print(next(g))

# 运行结果
<generator object <genexpr> at 0x10f13fc60>
0
1

可以循环打印

for x in g:
    print(x)

创建 generator:yield

定义一个 function

def fib(max):
    n, a, b = 0, 0, 1
    while n < max:
        print(b)
        a, b = b, a + b
        n = n + 1
    return 'done'
fib(5)

# 注意
a, b = b, a + b 的含义解释:
t = (b, a + b)  # t是一个tuple
a = t[0]
b = t[1]

Function改为generator,只需要在获取元素的地方改为yield,如果一个函数定义中包含yield关键字,那么这个函数就不再是一个普通函数,而是一个generator函数,调用一个generator函数将返回一个generator:

def fib_g(max):
    n, a, b = 0, 0, 1
    while n < max:
        yield b
        a, b = b, a + b
        n = n + 1
    return 'done'


f = fib_g(5)
print(f)

# 执行结果:
<generator object fib_g at 0x102d4c580>

generator函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

def odd():
    print('step 1')
    yield 1
    print('step 2')
    yield(3)
    print('step 3')
    yield(5)

g = odd()
next(g)
next(g)
next(g)

执行结果:
step 1
step 2
step 3

迭代 generator,基本上不会用 next() 来获取下一个返回值,而是直接使用 for 循环来迭代

g = odd()
for n in g:
    pass

执行结果:
step 1
step 2
step 3

获取 generator return 返回值

for 循环调用 generator 时,是拿不到 return 的返回值的。如果想要拿到,必须捕获 StopIteration 错误,返回值包含在StopIterationvalue 中:

g = odd()
while True:
    try:
        next(g)
    except StopIteration as e:
        print(e.value)
        break
执行结果:
step 1
step 2
step 3
over

iterable 和 iterator

iterable

直接作用于for循环的数据类型有以下几种:

  1. 集合数据类型,如listtupledictsetstr等;
  2. generator,包括生成器和带yieldgenerator function

这些可以直接作用于for循环的对象统称为可迭代对象:Iterableisinstance()判断一个对象是否是Iterable对象

print(isinstance('abc', Iterable)) # str是否可迭代
print(isinstance([1,2,3], Iterable)) # list是否可迭代
print(isinstance((x for x in range(10)), Iterable)) # list comprehensions 是否可迭代
print(isinstance(123, Iterable)) # 整数是否可迭代

迭代dict

from typing import Iterable

d = {'a': 1, 'b': 2, 'c': 3}
# 同时迭代key与value
for k, v in d.items():
    print(k, v)
# 默认迭代的是key
for k in d:
    print(k)
# 迭代value
for v in d.values():
    print(v)

for循环中同时迭代索引和元素本身

# Python内置的enumerate函数可以把一个list变成索引-元素对
for i,v in enumerate([1,2,3]):
    print(i, v)

iterator

iterator 不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一个值了。

isinstance()判断一个对象是否是Iterator对象:

print(isinstance((x for x in range(10)), Iterator)) # True
print(isinstance([], Iterator)) # False
print(isinstance({}, Iterator)) # False
print(isinstance('', Iterator)) # False

生成器都是Iterator对象,但listdictstr虽然是Iterable,却不是Iterator

listdictstrIterable变成Iterator可以使用iter()函数:

print(isinstance(iter([]), Iterator))  # True
print(isinstance(iter({}), Iterator))  # True

为何listdictstr等数据类型不是Iterator

Iterator对象表示的是一个数据流,Iterator对象可以被next()函数调用并不断返回下一个数据,直到没有数据时抛出StopIteration错误。可以把这个数据流看做是一个有序序列,但我们却不能提前知道序列的长度,只能不断通过next()函数实现按需计算下一个数据,所以Iterator的计算是惰性的,只有在需要返回下一个数据时它才会计算。

Iterator甚至可以表示一个无限大的数据流。而使用list是永远不可能存储全体自然数的。

函数式编程

Higher-order function 高阶函数

函数名实际上就是指向函数的变量

f = abs

print(f(-10))
print(type(f))

执行结果:
10
<class 'builtin_function_or_method'>

abs指向10后,就无法通过abs(-10)调用该函数,因为abs不指向求绝对值函数而是指向一个整数10

abs = 10
print(abs(-10))

注:由于abs函数是定义在import builtins模块中的,所以要让修改abs变量的指向在其它模块也生效,要用import builtins; builtins.abs = 10

传入函数

一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

def operate(x, y, f):
    return f([x,y])
print(operate(1, 2, sum))
print(operate(1, 2, min))

map/reduce

《MapReduce: Simplified Data Processing on Large Clusters》是由Jeffrey Dean和Sanjay Ghemawat于2004年发表的经典论文,介绍了Google提出的MapReduce编程模型和它在大规模数据处理中的应用。这篇论文的主要内容是如何通过MapReduce简化分布式计算任务,使其能够在大型计算集群上高效运行。

map

m_func应用到每一个list元素中

# m 是一个`Iterator`,`Iterator`是惰性序列,
# 因此通过`list()`函数让它把整个序列都计算出来并返回一个list。
m = map(m_func, [1, 2, 3, 4, 5])
list(m)
reduce

reduce把结果继续和序列的下一个元素做累积计算,其效果就是:

reduce(f, [x1, x2, x3, x4]) = f(f(f(x1, x2), x3), x4)

求和运算

from functools import reduce

def add(x,y):
    return x + y
print(reduce(add, [1,2,3]))
print(input("reduce function=>###########"))

str转换为int

DIGITS = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}

def str2int(s):
    def fn(x, y):
        return x * 10 + y
    def char2num(s):
        return DIGITS[s]
    return reduce(fn, map(char2num, s))

print(str2int('67656'))

ambda函数进一步简化

def lambdaStr2int(s):
    def char2num(s):
        return DIGITS[s]
    return reduce(lambda x, y: x * 10 + y, map(char2num, s))
print(str2int('1234'))

filter

map()类似,filter()也接收一个函数和一个序列,将函数作用于序列中的每个元素,来过滤元素。

过滤序列中的基数

def isOdd(n):
    return n % 2 == 1
# filter()函数返回的是一个Iterator,是一个惰性序列,所以需要用list()函数获取结果并返回list。
l1 = list(filter(isOdd, [1, 2, 4, 5, 6, 9, 10, 15]))
print(l1)

过滤字符串

def notEmpty(s):
    # s:"" 或 None 为False
    # s.strip():返回一个去除字符串两端空白字符的新字符串,包括空格、制表符(tab)、换行符等
    return s and s.strip()
l2 = list(filter(notEmpty, ['A', '', 'B', None, 'C', '  ']))
print(l2)

sorted

排序的核心是比较两个元素的大小。如果是数字,可直接比较,如果是字符串或者两个dict直接比较就是没有意义的,因此,比较的过程需要通过函数抽象出来。

python 内置排序

l1 = sorted([36, 5, -12, 9, -21])
print(l1)

# 可以接收一个key函数来实现自定义的排序,例如按绝对值大小排序
l1 = sorted([36, 5, -12, 9, -21], key=abs)
print(l1)

# 默认字符串排序是按照ASCII的大小比较
l1 = sorted(['bob', 'about', 'Zoo', 'Credit'])
print(l1)

忽略大小写,倒序排序

# sorted传入key函数,即可实现忽略大小写的排序
# reverse=True 倒序
# l1 = sorted(['bob', 'about', 'Zoo', 'Credit'],key=lambda x:x.lower, reverse=True)
l1 = sorted(['bob', 'about', 'Zoo', 'Credit'],key=str.lower, reverse=True)
print(l1)

元组序列排序

L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
# 按照名称排序
def by_name(t):
    return t[0]
L1 = sorted(L, key=by_name)
print(L1)
# 按照成绩排序
def by_score(t):
    return t[1]
L1 = sorted(L, key=by_score)
print(L1)

return function

直接计算返回结果

def calc_sum(*args):
    ax = 0
    for n in args:
        ax = ax + n
    return ax
s = calc_sum(1, 3, 5, 7, 9)
print(s)

懒加载,返回的是一个函数,触发调用

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum
s = lazy_sum(1, 3, 5, 7, 9)()
print(s)

closure

返回的函数持有创建函数的变量

def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

# 执行结果为 9 9 9 原因是返回函数内部的变量 i 已经变成了 3
f1, f2, f3 = count()
print(f1(), f2(), f3())

解决方案:创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

f1, f2, f3 = count()
print(f1(), f2(), f3())

nonlocal

nonlocal 关键字用于声明一个变量来自外层函数作用域(但不是全局作用域)。

def inc():
    x = 0
    def fn():
        # 仅读取x的值:
        return x + 1
    return fn

f = inc()
print(f()) # 1
print(f()) # 1

使用 nonlocal 关键字声明

def inc():
    x = 0
    def fn():
        # 声明 x 来自外层函数作用域,而非全局作用域
        nonlocal x
        x = x + 1
        return x
    return fn

f = inc()
print(f()) # 1
print(f()) # 2

anonymous function

不需要显式地定义函数,直接传入匿名函数。lambda x: x * x就是一个匿名函数,lambda表示匿名函数,冒号前面的x表示函数参数。匿名函数有个限制,就是只能有一个表达式,不用写return,返回值就是该表达式的结果。

l = list(map(lambda x: x * x, [1, 2, 3, 4, 5]))
print(l)

匿名函数也是一个函数对象,也可以把匿名函数赋值给一个变量,再利用变量来调用该函数

f = lambda x: x * x
print(f(3))

decorator 装饰器

def now():
    print(datetime.now())
f = now
# __name__属性,可以拿到函数名称
print(now.__name__) # now
print(f.__name__) # now

在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。 本质上,decorator就是一个返回函数的高阶函数

def log(func):
    # 接收任意数量的位置参数和关键字参数
    def wrapper(*args, **kw):
        # % 运算符用于字符串格式化,它允许我们将变量或表达式的值插入到字符串的特定位置
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper
# @log相当于执行了如下语句
log(now)()
@log
def now():
    print(datetime.now())
now()

如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数

def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

@log('invoke method')
def now():
    print(datetime.now())
now()
# @log('invoke method') 相当于执行了如下语句
log('invoke method')(now())

functools.wraps 是 Python 标准库 functools 模块中的一个装饰器工具,它的主要作用是保留被装饰函数的元信息(如函数名、文档字符串、注解等)。这就可以避免因装饰器覆盖原函数而丢失重要的元信息。

@log('invoke method')
def now():
    print(datetime.now())
# now 被 decorator __name__的值变成了 wrapper
print(now.__name__) # wrapper

一个完整的decorator的写法

def log(func):
    @functools.wraps(func)
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

带有参数的decorator写法

def log(text):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator

Partial function 偏函数

当函数的参数个数太多,需要简化时functools.partial可把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数

调用有默认参数的函数

print(int('101010', base=2))
print(int('12345', base=8))
print(int('12345', base=16))

自定义函数调用

def int2(x, base=2):
    return int(x, base)


print(int2("101010"))

使用偏函数调用

import functools
int2 = functools.partial(int, base=2)
print(int2("101010"))

实际上可以接收函数对象、*args**kw这3个参数

int2 = functools.partial(int, base=2)
相当于
kw = { 'base': 2 }
int('10010', **kw)

# 会把`10`作为`*args`的一部分自动加到左边
max2 = functools.partial(max, 10)


当调用max2函数
max2(5, 6, 7)
相当于
args = (10, 5, 6, 7)
max(*args)

Module 模块

Module 介绍

使用Module可以提高代码的可维护性,当一个模块编写完毕,就可以被其他地方引用。 一个.py文件就称之为一个模块(Module)。使用模块可以避免函数名和变量名冲突。但尽量不要与内置函数名字冲突。 Module包括Python内置的模块和来自第三方的模块。

mycompany
 ├─ web
 │  ├─ __init__.py
 │  ├─ utils.py
 │  └─ www.py
 ├─ __init__.py
 ├─ abc.py
 └─ utils.py
  • __init__.py文件是必须的,否则Python就将这个目录当成普通目录,而不是一个包目录。__init__.py内容可以为空,也可以有 python 代码,因为 __init__.py 本身也是一个模块,而它的名字就是 mycompany
  • www.py的模块名就是mycompany.web.www,两个文件utils.py的模块名分别是mycompany.utilsmycompany.web.utils
  • 创建模块时不能和Python自带的模块名称冲突。先查看系统是否已存在该模块,检查方法是在Python交互环境执行import abc。如果系统自带了sys模块,自己的模块也为sys.py,则将无法导入系统的sys模块。
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 导入 sys 模块
# sys模块有一个argv变量,用list存储了命令行的所有参数。argv至少有一个元素,因为第一个参数永远是该.py文件的名称
import sys

# 写入作者
__author__ = 'dawn'

' 模块文档注释 '

def test():
    args = sys.argv
    print(args)
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')
# 运行文件时,解释器将Python解释器把一个特殊变量
#__name__置为__main__,
# 而如果在其他地方导入该hello模块时,if判断将失败。
if __name__=='__main__':
    test()

正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;

__xxx__这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的__author____name__就是特殊变量,hello模块定义的文档注释也可以用特殊变量__doc__访问。我们自己的变量一般不要用这种变量名;

_xxx__xxx这样的函数或变量是private,不该被直接引用(可以引用,因为Python并没有一种方法可以完全限制访问private函数或变量,而从编程习惯上不应该引用private函数或变量);

def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)

第三方模块

在Python中,安装第三方模块,是通过包管理工具pip完成的,安装一个第三方库——Python Imaging Library,这是Python下非常强大的处理图像的工具库。不过,PIL目前只支持到Python 2.7,并且有年头没有更新了,而基于PIL的Pillow项目开发非常活跃,并且支持最新的Python 3。

一般,第三方库都会在Python官方的pypi.python.org网站注册,可以在官网或者pypi上搜索,比如Pillow

安装模块:pip install Pillow