Python入门 (二)

2023-01-31 00:01:55 python 入门

本文是个人python学习笔记学习资料为廖雪峰Python教程,如需更多内容,请移步廖老师官方网站。

一 函数式编程Functional Programming

函数式编程允许把函数本身作为参数传入另一个函数,还允许返回一个函数。Python对函数式编程提供部分支持。由于Python允许使用变量,因此,Python不是纯函数式编程语言

1.1 高阶函数 Higher-order function

变量可以指向函数:变量可以指向函数,并且通过这个变量去调用函数。

>>> f = abs
>>> f(-30)
30

函数名也是变量:对于函数abs( ),abs也可以看做看做指向求绝对值函数的变量。

传入函数:既然变量可以指向函数,函数的参数能接收变量,那么一个函数就可以接收另一个函数作为参数,这种函数就称之为高阶函数。

1.2 map() reduce()

map()函数接收两个参数,一个是函数,一个是Iterablemap将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。

reduce()把一个函数作用在一个序列[x1, x2, x3, ...]上,这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。

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

 1 #字符串转为int
 2 from functools import reduce
 3 D = {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
 4 
 5 def str2num(s):
 6     def fn(x,y):
 7         return x * 10 + y
 8 
 9     def char2num(c):
10         return D[c]
11     
12     return reduce(fn,map(char2num,s))
 1 #利用map()将不规范的姓名输入改为首字母大写,其他小写
 2 def nORMalize(name):
 3     def toUp(onename):
 4         return onename[0].upper()+onename[1:].lower()
 5     
 6     return list(map(toUp,name))
 7     #return list(map([i[0].upper+i[1:] for i in name],name))
 8 
 9 # 测试:
10 L1 = ['adam', 'LISA', 'barT']
11 L2 = normalize(L1)
12 print(L2)
1 #编写一个prod()函数,可以接受一个list并利用reduce()求积
2 def prod(L):
3     def fn(x,y):
4         return x * y
5 
6     return reduce(fn,L)
1 #利用map和reduce编写一个str2float函数,把字符串'123.456'转换成浮点数123.456
2 D = {'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'0':0}
3 def str2float(s):
4     i = s.index('.')
5     def chr2num(c):
6         return D[c]
7     return reduce(lambda x,y : x * 10 + y,map(chr2num,s[:i]+s[i+1:])) / (10**(len(s) - i - 1))

1.3 filter()

filter()函数用于过滤序列,filter()接收一个函数和一个序列,把传入的函数依次作用于每个元素,然后根据返回值是True还是False决定保留还是丢弃该元素。filter()函数返回一个Iterator,需要用list()转换为list。

 1 #回数是指从左向右读和从右向左读都是一样的数,例如12321,909。请利用filter()筛选出回数:
 2 def is_palindrome(n):
 3     s = str(n)
 4     l = len(s)//2
 5     i = 0
 6     while i < l:
 7         if(s[i] != s[-1-i]):
 8             return False
 9         i = i + 1
10     return True

1.4 sorted()

sorted()函数是一个高阶函数,它可以接收一个key函数来实现自定义的排序。字符串的排序是针对ASCII码的大小,大写字母小于小写字母。默认由小到大排序,若需反序,可加入参数reverse=True。

>>> sorted(['Lily','tom','Jack','bob'],key=str.lower,reverse=True)
['tom', 'Lily', 'Jack', 'bob']
1 #用一组tuple表示学生名字和成绩,用sorted()对其分别按名字排序:
2 L = [('Bob', 75), ('Adam', 92), ('Bart', 66), ('Lisa', 88)]
3 
4 def by_name(t):
5     return t[0]
6 
7 L2 = sorted(L, key=by_name)
8 print(L2)
#r如果对成绩由高到低排序
def by_score(t):
    return -t[1]

L2 = sorted(L, key=by_score)
print(L2)

1.5 返回函数

 高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回,相关参数和变量都保存在返回的函数中。

闭包:闭包是能够读取其他函数内部变量的函数,是将函数内部和函数外部连接起来的桥梁。如果在一个内部函数里,对在外部函数内(但不是在全局作用域)的变量进行引用,那么内部函数就被认为是闭包(closure)。需要注意:返回函数不要引用任何循环变量,或者后续会发生变化的变量,因为返回一个函数时,该函数并未执行,返回函数中不要引用任何可能会变化的变量。

 1 #利用闭包返回一个计数器函数,每次调用它返回递增整数
 2 def createCounter():
 3     i = 0
 4     def counter():
 5         nonlocal i #若想修改作用域E中声明的变量,需使用nonlocal关键字。作用域参考随笔《Python中的变量作用域》
 6         i += 1
 7         return i
 8     return counter
 9 
10 
11 # 测试:
12 counterA = createCounter() #返回counter()代码块及变量i=1
13 print(counterA(), counterA(), counterA(), counterA(), counterA()) # 1 2 3 4 5 每次执行counterA()时,就是执行counter()代码块,改变i的值
14 counterB = createCounter() #返回counter()代码块及变量i=1
15 if [counterB(), counterB(), counterB(), counterB()] == [1, 2, 3, 4]:
16     print('测试通过!')
17 else:
18     print('测试失败!')
1 #createCounter()也可以用list实现
2 def createCounter2():
3     f = [0]
4     def counter():
5         f[0] += 1  #list是可变对象
6         return f[0]
7     return counter

1.6 匿名函数

关键字lambda表示匿名函数,冒号左边的表示函数参数,右边为return的表达式,只能有一个表达式。匿名函数可以赋值给一个变量,通过变量来调用该函数。也可以将匿名函数作为函数返回值返回。

>>> f = lambda x,y : x + y
>>> f
<function <lambda> at 0x00000000007F1400>
>>> f(2,3)
5
1 #利用lambda求奇数
2 L = list(filter(lambda x : x % 2 == 1, range(1, 20)))

1.7 装饰器

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator),这种理念类似JAVA设计模式中的装饰模式。用@作为函数的修饰符,写在函数定义的前一行,它将被修饰的函数作为参数,并返回修饰后的同名函数或其他可调用的东西。

如果Decorator没有参数:

    @dec2  
    @dec1  
    def func(arg1, arg2, ...):  
        pass 

相当于:

def func(arg1, arg2, ...):  
    pass  
func = dec2(dec1(func)) 

如果Decorator有参数:

@dec('abc') 
def func(arg1, arg2, ...):  
    pass 

相当于:

def func(arg1, arg2, ...):  
    pass 

func = dec('abc')(func)

以有参数的案例解释执行情况:

 1 def log(text):
 2     def decorator(func):
 3         def wrapper(*args, **kw):
 4             print('%s %s():' % (text, func.__name__))
 5             return func(*args, **kw)
 6         return wrapper
 7     return decorator
 8 
 9 @log('execute')
10 def now():
11     print('2015-3-25')

运行结果:

>>> now()
execute now():
2015-3-25

这个案例中,now = log('execute')(now),执行过程如下图:

 此时,now.__name__是wrapper,需要把原始函数的__name__等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。方法是import functools,在定义wrapper()的前面加上@functools.wraps(func)。

 1 import functools
 2 
 3 def log(text):
 4     def decorator(func):
 5         @functools.wraps(func)
 6         def wrapper(*args, **kw):
 7             print('%s %s():' % (text, func.__name__))
 8             return func(*args, **kw)
 9         return wrapper
10     return decorator
 1 import time, functools
 2 
 3 #请设计一个decorator,它可作用于任何函数上,并打印该函数的执行时间
 4 def metric(fn):
 5     @functools.wraps(fn)
 6     def wrapper(*args,**kw):
 7         begin = time.time()
 8         n = fn(*args,**kw)
 9         cost = time.time() - begin
10         print('%s executed in %s ms' % (fn.__name__, cost))
11         return fn(*args,**kw)
12     return wrapper
13 
14 # 测试
15 @metric
16 def fast(x, y):
17     time.sleep(0.0012)
18     return x + y;
19 
20 @metric
21 def slow(x, y, z):
22     time.sleep(0.1234)
23     return x * y * z;
24 
25 f = fast(11, 22)
26 s = slow(11, 22, 33)
27 if f != 33:
28     print('测试失败!')
29 elif s != 7986:
30     print('测试失败!')

1.8 偏函数

functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。创建偏函数时,实际上可以接收函数对象、*args**kw这3个参数。

二 模块

在Python中,一个.py文件就称之为一个模块(Module),提高了代码的可维护性,可以避免函数名和变量名冲突(尽量不与内置函数冲突)。

按目录来组织模块的方法,称为包(Package),可以避免模块名冲突。每一个包目录下面都会有一个__init__.py的文件,这个文件是必须存在的,否则,Python就把这个目录当成普通目录,而不是一个包。__init__.py可以是空文件,也可以有Python代码,因为__init__.py本身就是一个模块,而它的模块名就是mycompany。包可以组织成多级目录:

mycompany
 ├─ WEB
 │  ├─ __init__.py
 │  ├─ utils.py
 │  └─ www.py
 ├─ __init__.py
 ├─ abc.py
 └─ xyz.py

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

 作用域:

  • 正常的函数和变量名是公开的(public),可以被直接引用,比如:abcx123PI等;
  • __xxx__ 是特殊变量,可以被直接引用,但是有特殊用途,例如__author____name__,以及模块文档注释__doc__;
  • _xxx__xxx这样的函数或变量是非公开的(private),不应该被直接引用,比如_abc__abc等;

安装第三方模块:

知道模块名称后,可用以下命令进行安装:pip install 模块名。推荐使用Anaconda,他已将数十个第三方模块安装在自己的路径下。

import某个模块时,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中。添加搜索目录的两种方式:

1.修改sys.path,这种方法在运行时修改,运行结束后失效。

>>> import sys
>>> sys.path.append('/Users/michael/my_py_scripts')

2.设置环境变量PYTHONPATH,该环境变量的内容会被自动添加到模块搜索路径中

 

面向对象编程

Python中类定义示例:

class Student(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score

    def print_score(self):
        print('%s: %s' % (self.name, self.score))

类名大写开头,object为集成的类,如无继承类,则直接用object。__init__为构造函数,self相当于this。

和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同。

 访问限制:

在Python中,实例的变量名如果以双下划线__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。__xxx__是特殊变量,可以被直接访问。

继承与多态

子类可以继承父类的所有功能,并重写父类函数。

多态:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保类方法run()方法编写正确,不用管原来的代码是如何调用的,运行时会自动调用相应的类方法。

获取对象信息

可以使用type()函数获取对象类型信息,返回对应的Class类型。基础数据类型int、str等。判断是否是函数:

>>> import types
>>> def fn():
...     pass
...
>>> type(fn)==types.FunctionType
True
>>> type(abs)==types.BuiltinFunctionType
True
>>> type(lambda x: x)==types.LambdaType
True
>>> type((x for x in range(10)))==types.GeneratorType
True

对于继承关系,可以用isinstance(对象名,类名)函数。还可以判断是都某些类型中的一种

>>> isinstance([1, 2, 3], (list, tuple))
True

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list。

>>> dir('ABC')
['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']

配合getattr()setattr()以及hasattr(),可以直接操作一个对象的状态。

实例属性和类属性

可以直接在class中定义属性,这种属性是类属性,归Student类所有:

class Student(object):
    name = 'Student'

这个属性虽然归类所有,但类的所有实例都可以访问到,coding时千万不要对实例属性和类属性使用相同的名字。

四 面向对象高级编程

4.1 __slots__

创建实例后,可动态实例绑定任何属性和方法。

>>> s = Student()
>>> s.name = 'Michael' # 动态给实例绑定一个属性
>>> print(s.name)
Michael

给实例绑定方法时,绑定的方法只对该实例有效,对其他实例不生效:

>>> def set_age(self, age): # 定义一个函数作为实例方法
...     self.age = age
...
>>> from types import MethodType
>>> s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
>>> s.set_age(25) # 调用实例方法
>>> s.age # 测试结果
25

若要对所有实例生效,则需要给类绑定方法:

>>> def set_score(self, score):
...     self.score = score
...
>>> Student.set_score = set_score

定义class时,定义一个特殊的__slots__变量,用来限制该class实例能添加的属性。这种限制对继承的子类不生效。若要对子类生效,则需要在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__

class Student(object):
    __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
>>> s = Student() # 创建新的实例
>>> s.name = 'Michael' # 绑定属性'name'
>>> s.age = 25 # 绑定属性'age'
>>> s.score = 99 # 绑定属性'score'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'score'

4.2 @property

 @property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。Python内置的@property装饰器就是负责把一个方法变成属性调用的,只定义getter方法,不定义setter方法就是一个只读属性。

 1 #利用@property给一个Screen对象加上width和height属性,以及一个只读属性resolution
 2 class Screen(object):
 3     @property
 4     def width(self):
 5         return self._width
 6 
 7     @width.setter
 8     def width(self,value):
 9         self._width = value
10 
11     @property
12     def height(self):
13         return self._height
14 
15     @height.setter
16     def height(self,value):
17         self._height = value
18 
19     @property
20     def resolution(self):
21         return self._width * self._height

4.3 多重继承

一个子类集成多个父类,同时获得多个父类的所有功能,就是多重继承。MixIn就是一种常见的设计,用于命名是更好的看出继承关系。

class Dog(Mammal, RunnableMixIn, CarnivorousMixIn):
    pass
class MytcpServer(TCPServer, ForkingMixIn):
    pass

 4.4 定制类

__str__()方法,返回一个好看的字符串。__str__()返回用户看到的字符串,而__repr__()返回程序开发者看到的字符串,是为调试服务的。

class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return 'Student object (name=%s)' % self.name
    __repr__ = __str__

__iter__方法返回一个迭代对象,or循环就会不断调用该迭代对象的__next__()方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环。

lass Fib(object):
    def __init__(self):
        self.a, self.b = 0, 1 # 初始化两个计数器a,b

    def __iter__(self):
        return self # 实例本身就是迭代对象,故返回自己

    def __next__(self):
        self.a, self.b = self.b, self.a + self.b # 计算下一个值
        if self.a > 100000: # 退出循环的条件
            raise StopIteration()
        return self.a # 返回下一个值

__getitem__方法可以让实例像list一样访问:

class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

相应的,还有__setitem__()方法,把对象视作list或dict来对集合赋值。最后,还有一个__delitem__()方法,用于删除某个元素

 __getattr__方法可以动态返回属性或函数,当调用类的方法或属性时,若不存在,才会在__getattr__中查找。可利用完全动态的__getattr__,实现链式调用:

class Chain(object):

    def __init__(self, path=''):
        self._path = path

    def __getattr__(self, path):
        return Chain('%s/%s' % (self._path, path))

    def __str__(self):
        return self._path

    __repr__ = __str__
>>> Chain().status.user.timeline.list
'/status/user/timeline/list'

 __call__方法允许我们对实例进行调用,可以把对象看待为函数。

class Student(object):
    def __init__(self, name):
        self.name = name

    def __call__(self):
        print('My name is %s.' % self.name)
>>> s = Student('Michael')
>>> s() # self参数不要传入
My name is Michael.

可用callable()函数判断一个对象是否可条用:

>>> callable(Student())
True
>>> callable(max)
True
>>> callable([1, 2, 3])
False

 4.5 枚举类

枚举类value属性则是自动赋给成员的int常量,默认从1开始计数。

>>> from enum import Enum
>>> Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug
', 'Sep', 'Oct', 'Nov', 'Dec'))
>>> for name, member in Month.__members__.items():
...     print(name, '=>', member, ',', member.value)
...
Jan => Month.Jan , 1
Feb => Month.Feb , 2
Mar => Month.Mar , 3
Apr => Month.Apr , 4
May => Month.May , 5
Jun => Month.Jun , 6
Jul => Month.Jul , 7
Aug => Month.Aug , 8
Sep => Month.Sep , 9
Oct => Month.Oct , 10
Nov => Month.Nov , 11
Dec => Month.Dec , 12

可以从Enum派生出自定义类:

from enum import Enum, unique

@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

@unique装饰器用于检查保证没有重复值。

五 错误、调试和测试

 5.1 错误处理

可以用try...except...finally...捕捉错误,当try程序块出错时,由except捕捉,跳到except语句块,然后执行finally语句块。可以在except后面写else,没有错误发生时,自动执行else:

try:
    print('try...')
    r = 10 / int('2')
    print('result:', r)
except ValueError as e:
    print('ValueError:', e)
except ZeroDivisionError as e:
    print('ZeroDivisionError:', e)
else:
    print('no error!')
finally:
    print('finally...')
print('END')
try...
result: 5.0
no error!
finally...
END

所有的错误类型都继承自BaseException,当执行父类错误except之后,不会再之后后续子类错误except。常见的错误类型和继承关系参考https://docs.python.org/3/library/exceptions.html#exception-hierarchy。

logging模块可以记录错误信息之后,继续执行并正常退出,还可以通过配置,把错误信息记录到日志中,方便排查。

# err_logging.py

import logging

def foo(s):
    return 10 / int(s)

def bar(s):
    return foo(s) * 2

def main():
    try:
        bar('0')
    except Exception as e:
        logging.exception(e)

main()
print('END')

5.2 调试

断言:当表达式为false时,抛出AssertionError。

def foo(s):
    n = int(s)
    assert n != 0, 'n is zero!'
    return 10 / n

def main():
    foo('0')
$ python err.py
Traceback (most recent call last):
  ...
AssertionError: n is zero!

启动Python解释器时可以用-O参数来关闭assert,关闭后可以把所有assert当作pass来看:

$ python -O err.py
Traceback (most recent call last):
  ...
ZeroDivisionError: division by zero

logging:logging不会抛出错误,而且可以输出到文件。logging有debuginfowarningerror等几个级别,每次只能设置一个级别的输出。通过简单的配置,一条语句可以同时输出到不同的地方,比如console和文件。

import logging
logging.basicConfig(level=logging.INFO)

s = '0'
n = int(s)
logging.info('n = %d' % n)
print(10 / n)
$ python err.py
INFO:root:n = 0
Traceback (most recent call last):
  File "err.py", line 8, in <module>
    print(10 / n)
ZeroDivisionError: division by zero

pdb:可以单步调试

# err.py
s = '0'
n = int(s)
print(10 / n)

运行方式:

$ python -m pdb err.py
> /Users/michael/GitHub/learn-python3/samples/debug/err.py(2)<module>()
-> s = '0'

->表示下一步要执行的代码,输入l可以查看代码,输入n单步执行,输入p 变量名查看变量,输入q结束调试。

pdb.set_trace():设置断点,

# err.py
import pdb

s = '0'
n = int(s)
pdb.set_trace() # 运行到这里会自动暂停
print(10 / n)

运行方法:

$ python err.py 
> /Users/michael/github/learn-python3/samples/debug/err.py(7)<module>()
-> print(10 / n)
(Pdb) p n
0
(Pdb) c
Traceback (most recent call last):
  File "err.py", line 7, in <module>
    print(10 / n)
ZeroDivisionError: division by zero

输入p 变量名查看变量,输入c继续执行。

IDE:

目前比较好的Python IDE有:

Visual Studio Code:Https://code.visualstudio.com/,需要安装Python插件

PyCharm:http://www.jetbrains.com/pycharm/

另外,Eclipse加上pydev插件也可以调试Python程序。

5.3 单元测试

测试用例放到一个测试模块里,就是一个完整的单元测试。以测试为驱动的开发模式最大的好处就是确保一个程序模块的行为符合我们设计的测试用例。在将来修改的时候,可以极大程度地保证该模块行为仍然是正确的。测试类从unittest.TestCase继承,以test开头的方法就是测试方法,不以test开头的方法不被认为是测试方法,测试的时候不会被执行。unittest.TestCase提供的内置判断条件:

self.assertEqual(abs(-1), 1) # 断言函数返回的结果与1相等
with self.assertRaises(KeyError): #通过d['empty']访问不存在的key时,断言会抛出KeyError
    value = d['empty']
with self.assertRaises(AttributeError): #通过d.empty访问不存在的key时,我们期待抛出AttributeError
    value = d.empty
self.assertTrue('key' in d) #断言d中存在key

 运行:方法一:加上以下两行代码,直接运行脚本:

if __name__ == '__main__':
    unittest.main()


方法二:使用参数-m unittest,此方法可批量运行测试脚本
$ python -m unittest mydict_test
.....
----------------------------------------------------------------------
Ran 5 tests in 0.000s

OK

 5.4 文档测试

Python内置的“文档测试”(doctest)模块可以直接提取注释中的代码并执行测试。doctest严格按照Python交互式命令行的输入和输出来判断测试结果是否正确。只有测试异常的时候,可以用...表示中间一大段烦人的输出。

# mydict2.py
class Dict(dict):
    '''
    Simple dict but also support access as x.y style.

    >>> d1 = Dict()
    >>> d1['x'] = 100
    >>> d1.x
    100
    >>> d1.y = 200
    >>> d1['y']
    200
    >>> d2 = Dict(a=1, b=2, c='3')
    >>> d2.c
    '3'
    >>> d2['empty']
    Traceback (most recent call last):
        ...
    KeyError: 'empty'
    >>> d2.empty
    Traceback (most recent call last):
        ...
    AttributeError: 'Dict' object has no attribute 'empty'
    '''
    def __init__(self, **kw):
        super(Dict, self).__init__(**kw)

    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Dict' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

if __name__=='__main__':
    import doctest
    doctest.testmod()

 

相关文章