python 继续

Posted by zluckyH on September 5, 2016

学习了跟老齐学python,一些基础的知识是掌握了,接下来继续深入学习,廖雪峰的python教程中有一些相对高级一点的内容,包括面向对象编程进程和线程电子邮件web开发等,在现有的基础上学习这些知识,我觉得刚刚好。


day1 函数式编程——2016.9.5

收集参数 * and **

当函数的参数个数不确定时,其它参数全部通过*arg,以元组的形式由arg收集起来,例子如下:

def func(x, *arg):
    print x         #Python 3请自动修改为print()的格式,下同,从略。
    result = x
    print arg       #输出通过*arg方式得到的值
    for i in arg:
        result +=i
    return result

print func(1, 2, 3, 4, 5, 6, 7, 8, 9)    #赋给函数的参数个数不仅仅是2个

运行结果:

1                       #这是函数体内的第一个print,参数x得到的值是1
(2, 3, 4, 5, 6, 7, 8, 9) #这是函数内的第二个print,参数arg得到的是一个元组
45                      #最后的计算结果

上面的例子中:

  • 值1传给了x

  • 值2,3,4,5,6,7,8,9被塞入一个元组里面,传给了arg

除了用*args这种形式的参数接收多个值之外,还可以用**kargs的形式接收数值,例如:

>>> def foo(**kargs):
...     print kargs        #Python 3:  print(kargs)
...
>>> foo(a=1,b=2,c=3)    #注意观察这次赋值的方式和打印的结果
{'a': 1, 'c': 3, 'b': 2}

如果用**kargs的形式收集值,会得到dict类型的数据,但是,需要在传值的时候说明“键”和“值”,因为在字典中是以键值对形式出现的。

综合以上方法,对于不确定参数个数的函数:

>>> def foo(x,y,z,*args,**kargs):
...     print x       #Python 3用户请修改为print()格式,下同
...     print y
...     print z
...     print args
...     print kargs        
... 
  #结果
>>> foo(1,2,3,4,5,name="qiwsir")
1
2
3
(4, 5)
{'name': 'qiwsir'}

返回函数

对于通常的函数,都会返回结果,例如:

def calc_sum(*args):
    s = 0
    for x in args:
        s +=x
    return s


calc_sum(1,2,3,4,5)
Out[17]: 15

但是,如果不需要立刻求和,而是在后面的代码中,根据需要再计算怎么办?可以不返回求和的结果,而是返回求和的函数!

def lazy_sum(*args):
    def sum():
        s = 0
        for x in args:
            s += x
        return s
    return sum


f = lazy_sum(1,2,3,4,5)

f
Out[20]: <function __main__.sum>

f()
Out[21]: 15

闭包

一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。我们来看一个例子:

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


f1,f2,f3 = count()

f1
Out[25]: <function __main__.f>

f1(),f2(),f3()
Out[27]: (9, 9, 9)

你可能认为调用f1()f2()f3()结果应该是149,但实际结果都是9,原因就在于返回的函数引用了变量i,但它并非立刻执行。等到3个函数都返回时,它们所引用的变量i已经变成了3,因此最终结果为9

如果一定要引用循环变量怎么办?方法是再创建一个函数,用该函数的参数绑定循环变量当前的值,无论该循环变量后续如何更改,已绑定到函数参数的值不变:

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


f1,f2,f3 = count()

f1()
Out[30]: 1

f2()
Out[31]: 4

f3()
Out[32]: 9

装饰器

假设我们要增强一个函数例如now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

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

观察上面的log,因为它是一个decorator,所以接受一个函数作为参数,并返回一个函数。我们要借助Python的@语法,把decorator置于函数的定义处:

@log
def now():
    print '2016-9-5'

调用now()函数,不仅会运行now()函数本身,还会在运行now()函数前打印一行日志:

>>> now()
call now():
2016-9-5

偏函数

Python的functools模块提供了很多有用的功能,其中一个就是偏函数(Partial function)。functools.partial就是帮助我们创建一个偏函数的,简单总结functools.partial的作用就是,把一个函数的某些参数给固定住(也就是设置默认值),返回一个新的函数,调用这个新函数会更简单。

>>> import functools
>>> int2 = functools.partial(int, base=2)
>>> int2('1000000')
64
>>> int2('1010101')
85

面向对象编程

类和实例

面向对象的设计思想是从自然界中来的,因为在自然界中,类(Class)和实例(Instance)的概念是很自然的。Class是一种抽象概念,比如我们定义的Class——Student,是指学生这个概念,而实例(Instance)则是一个个具体的Student,比如,Bart Simpson和Lisa Simpson是两个具体的Student

所以,面向对象的设计思想是抽象出Class,根据Class创建Instance。

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)
    def get_grade(self):
        if self.score >= 90:
            return 'A'
        elif self.score >=60:
            return 'B'
        else:
            return 'C'
        

bart = Student('bart simpson',59)

bart.get_grade
Out[116]: <bound method Student.get_grade of <__main__.Student object at 0x0000000003CDB908>>

bart.get_grade()
Out[117]: 'C'

访问限制

从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、score属性:

>>> bart = Student('Bart Simpson', 98)
>>> bart.score
98
>>> bart.score = 59
>>> bart.score
59

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线__,在Python中,实例的变量名如果以__开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问,所以,我们把Student类改一改:

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)



>>> bart = Student('Bart Simpson', 98)
>>> bart.__name
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__name'

外部无法访问实例变量.__name实例变量.__score,但是如果外部代码要获取name和score怎么办?可以给Student类增加get_nameget_score这样的方法:

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)

    def get_name(self):
        return self.__name

    def get_score(self):
        return self.__score 

如果又要允许外部代码修改score怎么办?可以给Student类增加set_score方法(可以对参数做检查,避免传入无效的参数:):

class Student(object):
    ...

    def set_score(self, score):
    	if 0 <= score <= 100:
    		self.__score = score
    	else:
    		raise ValueError('bad score')

继承和多态

面向对象高级编程

使用__slots__

动态绑定允许我们在程序运行的过程中动态给class加上功能,但是,如果我们想要限制class的属性怎么办?比如,只允许对Student实例添加name和age属性。 为了达到限制的目的,Python允许在定义class的时候,定义一个特殊的__slots__变量,来限制该class能添加的属性:

>>> 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'

由于'score'没有被放到__slots__中,所以不能绑定score属性,试图绑定score将得到AttributeError的错误。

使用@property

Python内置的@property装饰器是负责把一个方法变成属性调用的,它可以让Python做到既能检查参数,又可以用类似属性这样简单的方式来访问类的变量:

class Student(object):
    @property
    def score(self):
        return self.__score
    @score.setter    
    def score(self,value):
        if not isinstance(value,int):
            raise ValueError('score must be an integer')
        if value<0 or value>100:
            raise ValueError('score must between 0~100')
        self.__score = value

把一个getter方法变成属性,只需要加上@property就可以了,此时,@property本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有一个可控的属性操作:

>>> s = Student()
>>> s.score = 60 # OK,实际转化为s.set_score(60)
>>> s.score # OK,实际转化为s.get_score()
60
>>> s.score = 9999
Traceback (most recent call last):
  ...
ValueError: score must between 0 ~ 100!

还可以定义只读属性,只定义getter方法,不定义setter方法就是一个只读属性:

class Student(object):
    @property
    def birth(self):
        return self._birth
    @birth.setter
    def birth(self,value):
        self._birth = value
    @property
    def age(self):
        return 2016 - self._birth
  
s.birth = 1991

s.birth
Out[156]: 1991

s.age
Out[157]: 25

s.age = 30

AttributeErrorTraceback (most recent call last)
<ipython-input-158-de6598fd2649> in <module>()
----> 1 s.age = 30

AttributeError: can't set attribute

上面的birth是可读写属性,而age就是一个只读属性,因为age可以根据birth和当前时间计算出来。

多重继承

定制类

前面已经见到过__slots____len__这样的特殊函数,前者是为了限制类的属性,后者允许class作用于len()函数, 除此之外,Python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。

__str__

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

s = Student('michael')

s
Out[20]: <__main__.Student at 0x3fa1780>

print s
Student object (name: michael)   
     

__str__让打印出来的实例,不但好看,而且容易看出实例内部重要的数据。

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

s = Student('michale')

s
Out[25]: Student object (name: michale)

__iter__

__getattr__

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错,为了避免这个错误,可以使用__getattr__方法,该方法动态返回一个属性

class Student(object):

    def __init__(self):
        self.name = 'Michael'

    def __getattr__(self, attr):
        if attr=='score':
            return 99

>>> s = Student()
>>> s.name
'Michael'
>>> s.score
99

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.uer.timeline.list
Out[97]: /status/uer/timeline/list

使用元类

type()

type()函数既可以返回一个对象的类型,又可以创建出新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(object)...的定义:

def fn(self,name = 'world'):
    print 'hello, %s' %name
    

Hello = type('Hello',(object,),dict(hello = fn))

h = Hello()

h.hello()
hello, world

print type(Hello)
<type 'type'>

print type(h)
<class '__main__.Hello'>

metaclass

metaclass,直译为元类,简单的解释就是:

当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。 连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

所以,metaclass允许你创建类或者修改类。换句话说,你可以把类看成是metaclass创建出来的“实例”。

我们先看一个简单的例子,这个metaclass可以给我们自定义的MyList增加一个add方法:

定义ListMetaclass,按照默认习惯,metaclass的类名总是以Metaclass结尾,以便清楚地表示这是一个metaclass:

# metaclass是创建类,所以必须从`type`类型派生:
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['add'] = lambda self, value: self.append(value)
        return type.__new__(cls, name, bases, attrs)

class MyList(list):
    __metaclass__ = ListMetaclass # 指示使用ListMetaclass来定制类

当我们写下__metaclass__ = ListMetaclass语句时,魔术就生效了,它指示Python解释器在创建MyList时,要通过ListMetaclass.__new__()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

错误、调试和测试

进程和线程

进程:对于操作系统来说,一个任务就是一个进程(Process),比如打开一个浏览器就是启动一个浏览器进程,打开一个记事本就启动了一个记事本进程,打开两个记事本就启动了两个记事本进程,打开一个Word就启动了一个Word进程。

线程:有些进程还不止同时干一件事,比如Word,它可以同时进行打字、拼写检查、打印等事情。在一个进程内部,要同时干多件事,就需要同时运行多个“子任务”,我们把进程内的这些“子任务”称为线程(Thread)。

多进程

#常用内建函数

collections

namedtuple

如果想要表示一个坐标,可以使用namedtuple,

>>> from collections import namedtuple
>>> Point = namedtuple('Point', ['x', 'y'])
>>> p = Point(1, 2)
>>> p.x
1
>>> p.y
2

namedtuple是一个函数,它用来创建一个自定义的tuple对象,并且规定了tuple元素的个数,并可以用属性而不是索引来引用tuple的某个元素。

deque

deque是为了高效实现插入和删除操作的双向列表,适合用于队列和栈:

>>> from collections import deque
>>> q = deque(['a', 'b', 'c'])
>>> q.append('x')
>>> q.appendleft('y')
>>> q
deque(['y', 'a', 'b', 'c', 'x'])

OrderedDict

使用dict时,Key是无序的。在对dict做迭代时,我们无法确定Key的顺序。如果要保持Key的顺序,可以用OrderedDict

>>> from collections import OrderedDict
>>> d = dict([('a', 1), ('b', 2), ('c', 3)])
>>> d # dict的Key是无序的
{'a': 1, 'c': 3, 'b': 2}
>>> od = OrderedDict([('a', 1), ('b', 2), ('c', 3)])
>>> od # OrderedDict的Key是有序的
OrderedDict([('a', 1), ('b', 2), ('c', 3)])

Counter

Counter是一个简单的计数器,例如,统计字符出现的个数:

>>> from collections import Counter
>>> c = Counter()
>>> for ch in 'programming':
...     c[ch] = c[ch] + 1
...
>>> c
Counter({'g': 2, 'm': 2, 'r': 2, 'a': 1, 'i': 1, 'o': 1, 'n': 1, 'p': 1})

base64

Base64是一种用64个字符来表示任意二进制数据的方法。