Python进阶

本文主要是基于慕课网课程《python进阶》学习,并做一些总结。该课程详细介绍Python强大的函数式编程和面向对象编程,掌握Python高级程序设计的方法。

1. 函数式编程

所谓函数式编程的定义,可参考知乎上的详细回答,戳这里
Python的函数式编程主要有以下特点:

  • 不是纯函数式编程:允许有变量
  • 支持高阶函数:函数也可以作为变量传入
  • 支持闭包:有了闭包就能返回函数
  • 有限度地支持匿名函数

高阶函数的定义

  • 变量可以指向函数
  • 函数名其实就是指向函数的变量
  • 一个函数可以接收另一个函数作为参数
  • 能接收函数作为参数的函数就是高阶函数
    e.g.
def add(x, y, f):
    return f(x) + f(y)

>> add(-5, 9, abs)

map函数

Python内置的高阶函数,它接收一个函数 f 和一个 list,并通过把函数 f 依次作用在 list 的每个元素上,得到一个新的 list 并返回。

def f(x):
    return x*x
print map(f, [1, 2, 3, 4, 5, 6, 7, 8, 9])

>>>[1, 4, 9, 10, 25, 36, 49, 64, 81]

reduce函数

reduce()函数也是Python内置的一个高阶函数。reduce()函数接收的参数,一个函数 f,一个list。函数 f 必须接收两个参数,reduce()list的每个元素反复调用函数f,并返回最终结果值。reduce()还可以接收第3个可选参数,作为计算的初始值。

def f(x, y):
    return x + y
# 先计算头两个元素:f(1, 3),结果为4;
# 再把结果和第3个元素计算:f(4, 5),结果为9;
# 再把结果和第4个元素计算:f(9, 7),结果为16;
# 再把结果和第5个元素计算:f(16, 9),结果为25;
# 由于没有更多的元素了,计算结束,返回结果25。
reduce(f, [1, 3, 5, 7, 9])
>> 25

### filter函数
 `filter()`函数是 Python 内置的另一个有用的高阶函数,`filter()`函数接收一个函数 f 和一个`list`,这个函数 f 的作用是对每个元素进行判断,返回 `True`或 `False`,`filter()`根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新`list`。
```python
def is_not_empty(s):
    return s and len(s.strip()) > 0
filter(is_not_empty, ['test', None, '', 'str', '  ', 'END'])
>> ['test', 'str', 'END']

sorted函数

sorted()也是一个高阶函数,它可以接收一个比较函数来实现自定义排序,比较函数的定义是,传入两个待比较的元素 x, y,如果 x 应该排在 y 的前面,返回 -1,如果 x 应该排在 y 的后面,返回 1。如果 x 和 y 相等,返回 0。

def cmp_ignore_case(s1, s2):
    if s1.upper() > s2.upper():
        return 1
    elif s1.upper() < s2.upper():
        return -1
    else:
        return 0
   
print sorted(['bob', 'about', 'Zoo', 'Credit'], cmp_ignore_case)
>> ['about', 'bob', 'Credit', 'Zoo']

闭包

def calc_sum(lst):
    def lazy_sum():
        return sum(lst)
    return lazy_sum

注意: 发现没法把 lazy_sum 移到calc_sum的外部,因为它引用了calc_sum的参数 lst

像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)
闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。

# 希望一次返回3个函数,分别计算1x1,2x2,3x3:
def count():
    fs = []
    for i in range(1, 4):
        def f():
             return i*i
        fs.append(f)
    return fs

f1, f2, f3 = count()
>> f1()
>> 9 # 因为f1现在才计算i*i,但现在i的值已经变为3

匿名函数

关键字lambda表示匿名函数,冒号前面表示参数。

>>> map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9])
[1, 4, 9, 16, 25, 36, 49, 64, 81]

匿名函数有个限制,就是只能有一个表达式,不写return,返回值就是该表达式的结果。
使用匿名函数,可以不必定义函数名,直接创建一个函数对象,很多时候可以简化代码

>>> sorted([1, 3, 9, 5, 0], lambda x,y: -cmp(x,y))
[9, 5, 3, 1, 0]

返回函数的时候,也可以返回匿名函数:

>>> myabs = lambda x: -x if x < 0 else x 
>>> myabs(-1)
1
>>> myabs(1)
1

装饰器

  1. 无参数decorator
    Python中decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。
    使用 decorator 用Python提供的 @ 语法,这样可以避免手动编写 f = decorate(f) 这样的代码。
def log(f):
#*args代表一个元组,** kw代表一个dict,变量名是可以随意改变的,主要用于可变参数
    def fn(*args, **kw):
        print 'call ' + f.__name__ + '()...'
        return f(*args, **kw)
    return fn

@log
def add(x, y):
    return x + y
print add(1, 2)
  1. 带参数decorator
    如果有的函数非常重要,希望打印出'[INFO] call xxx()...',有的函数不太重要,希望打印出'[DEBUG] call xxx()...',这时,log函数本身就需要传入'INFO'或'DEBUG'这样的参数,类似这样:
@log('DEBUG')
def my_func():
    pass

把上面的定义翻译成高阶函数的调用,就是:

my_func = log('DEBUG')(my_func)

上面的语句看上去还是比较绕,再展开一下:

log_decorator = log('DEBUG')
my_func = log_decorator(my_func)

上面的语句又相当于:

log_decorator = log('DEBUG')
@log_decorator
def my_func():
    pass

所以,带参数的log函数首先返回一个decorator函数,再让这个decorator函数接收my_func并返回新函数:

def log(prefix):
    def log_decorator(f):
        def wrapper(*args, **kw):
            print '[%s] %s()...' % (prefix, f.__name__)
            return f(*args, **kw)
        return wrapper
    return log_decorator

@log('DEBUG')
def test():
    pass
print test()

执行结果:

[DEBUG] test()...
None

有些时候,我们需要依赖函数名做一些逻辑。但是如上装饰器一番改动之后,函数名已经发生了修改。如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要把原函数的一些属性复制到新函数中。所以Python内置的functools可以用来自动化完成这个“复制”的任务:

def log(f):
    @functools.wraps(f)
    def wrapper(x):
        print 'call...'
        return f(x)
    return wrapper

偏函数

偏函数可以理解为函数的可替代函数,但是更加简化。functools.partial就是帮助我们创建一个偏函数,如下所示,可创建一个base为2的int函数:

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

>>> int2('1000000')
64
>>> int2('1010101')
85

2. 模块

一个python文件可以理解为一个模块;而一个文件夹可以理解为一个包,但是每一级文件夹下面都需要包含一个特殊的init.py文件。

导入模块

  • 使用import关键字导入整个模块。
import math
  • 如果希望导入math模块的某几个函数就行,那可以使用如下语法:
from math import pow,sin,log
  • 如果同时导入的函数存在同名冲突时,例如上面语句同时导入了logginglog函数,则可以给函数起别名来避免冲突:
from math import log
from logging import log as logger   # logging的log现在变成了logger
print log(10)   # 调用的是math的log
logger(10, 'import from logging')   # 调用的是logging的log
  • 支持动态导入:
try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

安装第三方模块

使用pip工具。模块名查询网站:https://pypi.org/

3. 面向对象编程

创建实例/类属性

Python是动态语音,对每个实例,都可以直接给他们的属性赋值:

xiaoming = Person()
xiaoming.name = 'Xiao Ming'
xiaoming.gender = 'Male'
xiaoming.birth = '1990-1-1'

类属性可以直接在class中定义:

class Person(object):
    address = 'Earth'
    def __init__(self, name):
        self.name = name
        Person.address = 'Earth'

初始化实例属性

使用特殊的__init__()方法,第一个参数必须是self(也可以为别的名字,但建议使用习惯用法)

class Person(object):
    def __init__(self, name, gender, birth):
        self.name = name
        self.gender = gender
        self.birth = birth

访问限制

私有属性通过双下划线开头(__)。但是,如果一个属性以__xxx__的形式定义,那它又可以被外部访问了,以__xxx__定义的属性在Python的类中被称为特殊属性,有很多预定义的特殊属性可以使用,通常我们不要把普通属性用__xxx__定义。

定义实例/类方法

实例的方法就是在类中定义的函数,它的第一个参数永远是 self,指向调用该方法的实例本身,其他参数和一个普通函数是完全一样的:

class Person(object):

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

    def get_name(self):
        return self.__name

定义类方法,需要使用@classmethod关键字:

class Person(object):
    count = 0
    @classmethod
    def how_many(cls):
        return cls.count
    def __init__(self, name):
        self.name = name
        Person.count = Person.count + 1

print Person.how_many()
p1 = Person('Bob')
print Person.how_many()

4. 类的继承

类的继承方式

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

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score

一定要用super(Student, self).__init__(name, gender)去初始化父类,否则,继承自 PersonStudent 将没有 namegender

多重继承

除了从一个父类继承外,Python允许从多个父类继承,称为多重继承。


class D(B, C):
    def __init__(self, a):
        super(D, self).__init__(a)
        print 'init D...'

获取对象信息

  • isinstance() 判断它是否是某种类型的实例
  • type()获取变量类型
  • dir() 函数获取变量的所有属性。如果已知一个属性名称,要获取或者设置对象的属性,就需要用 getattr() 和 setattr( )函数了

5. 定制类

_str_()和_repr_()

str()用于显示给用户,而repr()用于显示给开发人员

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

_cmp_()

自定义类的排序方式:

# 按照分数从高到底排序,分数相同的按名字排序。
class Student(object):

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

    def __str__(self):
        return '(%s: %s)' % (self.name, self.score)

    __repr__ = __str__

    def __cmp__(self, s):
        if not isinstance(s,Student):
            return cmp(self.name,str(s))
        if self.score == s.score:
            return cmp(self.name,s.name)
        else:
            return -cmp(self.score,s.score)

L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
print sorted(L)

_len_()

如果类表现得像一个list,可以通过__len__()自定义类的长度。
要让len()函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。
例如,我们写一个 Students 类,把名字传进去:

class Students(object):
    def __init__(self, *args):
        self.names = args
    def __len__(self):
        return len(self.names)

类型转换

如果要让自定义类支持有理数运算,转为intfloat等类型:

class Rational(object):
    def __init__(self, p, q):
        self.p = p
        self.q = q
    def __int__(self):
        return self.p // self.q

@property

可以将get/set方法装饰城属性调用:

class Student(object):
    def __init__(self, name, score):
        self.name = name
        self.__score = score
    @property
    def score(self):
        return self.__score
    @score.setter
    def score(self, score):
        if score < 0 or score > 100:
            raise ValueError('invalid score')
        self.__score = score

注意: 第一个score(self)get方法,用@property装饰,第二个score(self, score)set方法,用@score.setter装饰,@score.setter是前一个@property装饰后的副产品。

现在,就可以像使用属性一样设置score了:

>>> s = Student('Bob', 59)
>>> s.score = 60
>>> print s.score
60
>>> s.score = 1000
Traceback (most recent call last):
  ...
ValueError: invalid score

说明对score赋值实际调用的是 set方法。

_slots_

由于Python是动态语言,任何实例在运行期都可以动态地添加属性。
如果要限制添加的属性,就可以利用Python的一个特殊的__slots__来实现。__slots__是指一个类允许的属性列表:

class Student(object):
    __slots__ = ('name', 'gender', 'score')
    def __init__(self, name, gender, score):
        self.name = name
        self.gender = gender
        self.score = score

现在,对实例进行操作:

>>> s = Student('Bob', 'male', 59)
>>> s.name = 'Tim' # OK
>>> s.score = 99 # OK
>>> s.grade = 'A'
Traceback (most recent call last):
  ...
AttributeError: 'Student' object has no attribute 'grade'

__slots__的目的是限制当前类所能拥有的属性,如果不需要添加任意动态的属性,使用__slots__也能节省内存。

_call_

在Python中,函数其实是一个对象:

>>> f = abs
>>> f.__name__
'abs'
>>> f(-123)
123

由于 f 可以被调用,所以,f 被称为可调用对象。
所有的函数都是可调用对象。
一个类实例也可以变成一个可调用对象,只需要实现一个特殊方法__call__()
我们把 Person 类变成一个可调用对象:

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

    def __call__(self, friend):
        print 'My name is %s...' % self.name
        print 'My friend is %s...' % friend

现在可以对 Person 实例直接调用:

>>> p = Person('Bob', 'male')
>>> p('Tim')
My name is Bob...
My friend is Tim...

单看 p('Tim')你无法确定 p是一个函数还是一个类实例,所以,在Python中,函数也是对象,对象和函数的区别并不显著。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,585评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,283评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,760评论 0 324
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,461评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,280评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,268评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,656评论 3 385
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,322评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,629评论 1 293
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,691评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,445评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,299评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,694评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,982评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,244评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,642评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,829评论 2 335