2020-02-02python学习记录(3)-函数式编程&模块
四、函数式编程:
函数式编程,有利于代码的管理,后期的代码的迭代和修改。函数的封装参数的传递,返回值的返回。
高阶函数:
变量可以指向函数、函数名也可能是变量(abs取绝对值)
def add(x, y, f):
return f(x) + f(y)
把函数作为参数传入,这样的函数称为高阶函数,函数式编程就是指这种高度抽象的编程范式。
map/reduce:
Python内建了map()和reduce()函数。
map()函数接收两个参数,一个是函数,一个是Iterable,map将传入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
map()传入的第一个参数是mix,即函数对象本身。由于结果r是一个Iterator,Iterator是惰性序列,因此通过list()函数让它把整个序列都计算出来并返回一个list。
一个序列求和,就可以用reduce实现:
运算可以直接用Python内建函数sum(),没必要动用reduce。
filter:
Python内建的filter()函数用于过滤序列。
通过filter去除某些不需要的值
只保留偶数
filter()的作用是从一个序列中筛出符合条件的元素。由于filter()使用了惰性计算,所以只有在取filter()结果的时候,才会真正筛选并每次返回下一个筛出的元素。
sorted:
主要是排序:
sorted()也是一个高阶函数。用sorted()排序的关键在于实现一个映射函数。
要进行反向排序,不必改动key函数,可以传入第三个参数reverse=Trues
sorted传入key函数,即可实现忽略大小写的排序:
返回函数:
高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。
def lazy_sum(*args): def sum(): ax = 0 for n in args: ax = ax + n return ax return sum
匿名函数:
在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便。
list(map(lambda x: x * x, [1, 2, 3, 4, 5, 6, 7, 8, 9]))
相当于下面的代码:
转换代码:
def is_odd(n): return n % 2 == 1 L = list(filter(is_odd, range(1, 20))) x = list(filter(lambda x: x % 2 == 1, range(1,20))) print(x)
装饰器:
由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import time, functools def metric(fn): print('%s executed in %s ms' % (fn.__name__, 10.24)) return fn @metric def fast(x, y): time.sleep(0.0012) return x + y; @metric def slow(x, y, z): time.sleep(0.1234) return x * y * z; if __name__ == '__main__': f = fast(11, 22) s = slow(11, 22, 33)
偏函数:
Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。
int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换:
五、模块:
注意:自己创建模块时要注意命名,不能和Python自带的模块名称冲突。例如,系统自带了sys模块,自己的模块就不可命名为sys.py,否则将无法导入系统自带的sys模块。
使用模块:
Python本身就内置了很多非常有用的模块,只要安装完毕,这些模块就可以立刻使用。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- ' a test module ' __author__ = 'Michael Liao' __age__ = '23' import sys def test(): args = sys.argv if len(args)==1: print('Hello, %s!' % args[0]) elif len(args)==2: print('Hello, %s!' % args[1]) else: print('Too many arguments!') if __name__=='__main__': test()
作用域:
在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在Python中,是通过_前缀来实现的。
正常的函数和变量名是公开的(public),可以被直接引用,比如:abc,x123,PI等;
类似_xxx和__xxx这样的函数或变量就是非公开的(private),不应该被直接引用,比如_abc,__abc等;
安装第三方模块:
在Python中,安装第三方模块,是通过包管理工具pip完成的。
六、面向对象编程
主要是抽象对象的一些特性,实例化具体的一个里面的一个处理。
在Python中,所有数据类型都可以视为对象,当然也可以自定义对象。自定义的对象数据类型就是面向对象中的类(Class)的概念。
可以类比java的编程
类和实例:
面向对象最重要的概念就是类(Class)和实例(Instance),必须牢记类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
class Student(object): pass
class后面紧接着是类名,即Student,类名通常是大写开头的单词,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。
数据封装:
面向对象编程的一个重要特点就是数据封装。在上面的Student类中,每个实例就拥有各自的name和score这些数据.
>>> class stu(object):
... def __init__(self,name,age):
... self.name = name
... self.age = age
...
>>> lisa = stu('lisa',12)
>>> lisa.name
'lisa'
访问限制:
在Class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就隐藏了内部的复杂逻辑。
如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:
>>> class stu(object):
... def __init__(self,name,age):
... self.__name = name
... self.__age = age
...
前面添加两个__这个就不能外部修改内容:
改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.__name和实例变量.__age了:
>>> bart = Student('Bart Simpson', 59)
>>> bart.__name
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name是因为Python解释器对外把__name变量改成了_Student__name,所以,仍然可以通过_Student__name来访问__name变量:
继承和多态:
在OOP程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类(Subclass),而被继承的class称为基类、父类或超类(Base class、Super class)。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- class Animal(object): def run(self): print('Animal is running...') class dog(Animal): pass if __name__=='__main__': dog().run()
dog 继承Animal的属性,还有类是一个object,需要调run方法。
#!/usr/bin/env python3 # -*- coding: utf-8 -*- class Animal(object): def run(self): print('Animal is running...') class dog(Animal): def dog1(self): print('dog is ...') if __name__=='__main__': dog().dog1()
继承还可以一级一级地继承下来,就好比从爷爷到爸爸、再到儿子这样的关系。而任何类,最终都可以追溯到根类object,这些继承关系看上去就像一颗倒着的树。比如如下的继承树:
静态语言 vs 动态语言:
对于静态语言(例如Java)来说,如果需要传入Animal类型,则传入的对象必须是Animal类型或者它的子类,否则,将无法调用run()方法。
对于Python这样的动态语言来说,则不一定需要传入Animal类型。我们只需要保证传入的对象有一个run()方法就可以了.
小结
继承可以把父类的所有功能都直接拿过来,这样就不必重零做起,子类只需要新增自己特有的方法,也可以把父类不适合的方法覆盖重写。
动态语言的鸭子类型特点决定了继承不像静态语言那样是必须的。
获取对象信息:
使用type():
使用isinstance():
使用dir():
对于class的继承关系来说,使用type()就很不方便。我们要判断class的类型,可以使用isinstance()函数。
如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法.
实例属性和类属性:
由于Python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self变量:
#!/usr/bin/env python3 # -*- coding: utf-8 -*- class Student(object): def __init__(self, name): self.name = name s = Student('Bob') s.score = 90 if __name__=='__main__': s = Student('Bob') s.score = 90 print(s.score)
小结
实例属性属于各个实例所有,互不干扰;
类属性属于类所有,所有实例共享一个属性;
不要对实例属性和类属性使用相同的名字,否则将产生难以发现的错误。
七、面向对象高级编程
使用__slots__
正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性。
正常的情况下可以随意的添加
为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class实例能添加的属性:
class Student(object): __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
>>> class student(object): ... __slots__ = ('name','age') ... >>> stu = student() >>> stu.name = 'bob' >>> stu.age = 12 >>> stu.score = 12 Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'student' object has no attribute 'score' >>>
由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。
使用__slots__要注意,__slots__定义的属性仅对当前类实例起作用,对继承的子类是不起作用的:
class stu2(student): passs s2 = stu2() s2.score = 9999
除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__————
新的继承的不要添加__slots__属性
使用@property
可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:
注意到这个神奇的@property,我们在对实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法来实现的。
@property广泛应用在类的定义中,可以让调用者写出简短的代码,同时保证对参数进行必要的检查,这样,程序运行时就减少了出错的可能性。