【廖雪峰课程笔记】之Python进阶

第1章 函数式编程

1-1 Python之函数式编程简介


  1. 函数式:functional,是一种编程范式;
  2. 函数式编程特点:
  • 把计算视为函数而非指令;
  • 纯函数式编程:不需要变量,没有副作用,测试简单;
  • 支持高阶函数,代码简洁;
  1. Python的函数式编程特点:
  • 不是纯函数式编程,允许有变量
  • 支持高阶函数,函数可以作为变量传入
  • 支持闭包,可以返回函数;
  • 有限度地支持匿名函数


1-2 Python之高阶函数


  1. 变量可以指向函数,直接调用该变量,和调用函数的效果一样;
  2. 函数名其实是指向函数的变量,和普通的变量名没有区别,只是指向了一个函数对象;
  3. 高阶函数:能接收函数做参数的函数
  • 变量可以指向函数;
  • 函数的参数可以接收变量;


1-3 Python之把函数作为参数


  1. 定义函数:
    def add(x, y, f):
      return f(x) + f(y)
    
    add(-5,9,abs) = abs(-5) + abs(9) = 14
    
  2. 定义的函数add,其参数 x、y、f 都可以任意传入,若 f 传入其他函数,就可以得到不同的返回值。

1-4 Python之map()函数


  1. 概述:指定规则作用于list中每个元素,生成新list的高阶函数;
  2. 定义:接收一个含1个参数的函数f和一个list,将函数f依次作用于list的每个元素,得到新list。

1-5 Python之reduce()函数


  1. 概述:指定规则使list内部元素交互运算并返回结果值的高阶函数;
  2. 定义:接收一个含2个参数的函数f、一个list、一个可选参数,将list中的元素逐个调用函数f(第i个元素和第(i+1)个元素交互结果再与第(i+3)个元素交互)进行交互运算,返回结果值,可选参数表示初始计算值。

1-6 Python之filter()函数


  1. 概述:指定判断规则对list中元素进行筛选的高阶函数;
  2. 定义:接收一个判断函数f和一个list,对list中每个元素进行判断,返回由符合条件的元素组成的新list;
  3. math.sqrt(x):返回的是浮点型数据,无法根据其运算结果进行整数判断;
  4. str.strip(x):删除字符串str开头、结尾处的字符串x。若x为空则默认删除空白符(包括‘\n’ , ‘\r’ , ‘\t’, ‘ ’)。

1-7 Python之自定义排序函数


  1. 比较函数的定义:传入2个待比较的元素 x 和 y,若 x 应该排在 y 前面则返回 -1;若 x 应该排在 y 后面则返回1;若 x 与 y 相等则返回0;
  2. sorted()也是一个高阶函数,通过调用一个比较函数可以实现自定义排序。

1-8 Python之返回函数


  1. Python函数不仅可以返回int、str、list、dict等数据类型,还可以返回函数;
  2. 区分返回函数和返回值;
    def myabs():
        return abs   # 返回函数
    
    def myabs2(x):
        return abs(x)   # 返回函数调用的结果,返回值是一个数值
    
  3. demo:调用A时,返回函数a,调用a时,返回值;
    def A(n):
        def a():
            return n*n
        return a
    
    >>> A(2)
    .a at 0x1085fcea0>
    
    >>> A(2)()
    4
    
  4. 返回函数可以把一些计算延迟执行,因为调用时返回的是内层的函数,如demo中,调用A(2)时返回的是函数 a ;
  5. 返回函数时,切记:该函数并未执行、返回函数中不能引用任何可能会变化的变量。

1-9 Python之闭包


  1. 闭包:内层函数引用了外层函数的局部变量或参数,然后返回内层函数的情况;
  2. 闭包特点:确保引用的局部变量在函数返回后不能变。

1-10 Python之匿名函数


  1. 匿名函数用lambda表示,后面紧跟变量+冒号(:),冒号后是表达式。使用匿名函数可以不必定义函数名,直接创建一个函数对象,简化代码;
  2. 匿名函数有个限制,只能有一个表达式,不写return,返回值即为表达式的结果;
  3. 示例:
    • 高阶函数
    reduce(lambda x,y:x*y,[1,2,3,4,5,6,7,8,9]) = 362880
    
    • 赋值给变量再调用
    f = lambda x, y: x*y    
    f(3,4) = 12
    
    • 作为返回值
    def build(x,y):
      return lambda x,y:x*y
    


1-11 Python之装饰器


  1. 应用场景:定义了一个函数,想在运行时动态增加功能,但又不想改动函数本身的代码时,借助装饰器可以实现;
  2. 装饰器(decorator)本质上是一个高阶函数,它接受一个函数作为参数,并返回一个新函数;
  3. Python内置的@语法可以简化装饰器的调用,@+函数名表示装饰器;
  4. 装饰器的特点:极大地简化代码,避免每个函数编写重复性代码;
  5. 示例:传入一个函数func,返回一个函数wrapper,wrapper函数就是装修后的函数func。
    def log(func):
        def wrapper():
            print('call %s():' % func.__name__)
            return func()
        return wrapper
    


1-12 Python之无参数装饰器


# 无参数装饰器,即装饰器自身不带参数
import time  #导入包

def performance(f):  #定义一个装饰器,本例将函数名factorial传入给参数 f
    def wrapper(*args,**kw):  #定义装饰器返回的函数wrapper,wrapper为装饰过的函数factorial,其参数(*args,**kw)为函数factorial的参数n,本例中的设置表示可以接受任意类型和个数的参数
        t1 = time.time()  #记录开始时间,并赋值给t1
        r = f(*args,**kw)  # f 即为原函数factorial,即f(*args,**kw)执行的是factorial(n),本例中为:factorial(10),最后赋值给 r 
        t2 = time.time()  #记录结束时间,并赋值给t2
        print ('call %s() in %fs' % (f.__name__, (t1 - t2)))   打印 f 函数的名字,2个时间戳的差值为浮点型数值,用%fs匹配
        return r    #返回 r 即为返回factorial(n)
    return wrapper    #装饰器每次返回新函数,即为装饰过的函数factorial

@performance  #填加装饰器performance,且未带参数
def factorial(n):    #定义的函数
    return reduce(lambda x,y: x*y, range(1, n+1))    #函数返回的表达式

print (factorial(10))  #打印函数,参数为10
call factorial() in -0.003976s
3628800


1-13 Python之带参数装饰器


# 装饰器本身带参数,首先需要定义一个接受参数的decorator函数,即比正常的装饰器多一层
import time    #导入包

def performance(unit):    #首先定义一个接受装饰器自身参数的函数
    def perf_decorator(f):     
        def wrapper(*args,**kw):
            t1 = time.time()
            r = f(*args,**kw)
            t2 = time.time()
            t = (t2 - t1)*1000 if unit == 'ms' else (t2- t1)
            print ('call %s() in %f%s' % (f.__name__,t,unit))
            return r
        return wrapper
    return perf_decorator

@performance('ms')    #填加装饰器performance,带参数,传入一个字符串'ms'
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))

print (factorial(10))
call factorial() in 5.087137ms
3628800


1-14 Python之完善装饰器decorator


  1. 装饰器@decorator可以动态实现函数功能的增加,但经过@decorator装饰过的函数,与原函数会有些不同;
  2. 1-13的例子为例,函数factorial经过@decorator后,返回的是新函数wrapper,而不再是factorial,这样会改变函数factorial的name/doc等属性,若使调用者看不出一个函数经过了@decorator装饰,需要把原函数的一些属性复制到新函数中,如下:
    def log(f):
        def wrapper(*args, **kw):
            print ('call...')
            return f(*args, **kw)
        wrapper.__name__ = f.__name__
        wrapper.__doc__ = f.__doc__
        return wrapper
    
  3. 这样写decorator会很麻烦,而且很难把原有函数的所有必要属性都一个一个复制到新函数上,Python内置了functools(),通过functools.wraps可以自动化完成这个 '复制' 任务;
    def log(f):
        @functools.wraps(*args,**kw)
        def wrapper(*args, **kw):
            print ('call...')
            return f(*args, **kw)
        return wrapper
    
  4. 使用functools()时,需要明确:由于我们把原函数的参数改为了(*args,**kw),因此无法获取原函数的原始参数信息,即便我们采用固定参数来装饰只有一个参数的函数,也可能改变原函数的参数名,因为新函数wrapper的参数名始终是 'x',原函数定义的参数名不一定叫 'x'。
    def log(f):
        @functools.wraps(f)
        def wrapper(x):
            print ('call...')
            return f(x)
        return wrapper
    


1-15 Python之偏函数


  1. 当一个函数有很多参数时,调用者需要提供多个参数,若能减少参数个数,就可以简化调用者的负担;
  2. Python内置了functools.partial可以创建偏函数,即把一个参数多的函数变成一个参数少的新函数,少的参数在创建时指定默认值,这样新函数调用的难度就降低了,如下:
    int2 = functools.partial(int, base=2)  # 创建一个默认进制数为二进制的int函数;
    


第2章 模块

2-1 Python之模块


  1. 在Python中,一个.py文件就是一个模块(module),可以实现特定的功能;
  2. 模块特点:提高代码的可维护性;编写代码不必从零开始;复用性强,可以被其它地方调用;
  3. 使用模块可以避免函数名和变量名冲突,相同名字的函数和变量可以放在不同的模块中;
  4. 包:按目录的方式来组织模块的方法。可以避免模块冲突,相当于创建了一个顶层包名,只要包名不冲突,模块就不会冲突;
  5. 每个包目录下必须有一个__init__.py文件,否则Python会将该目录视为普通目录,而不是包。init.py可以是空的,也可以有代码,它本身就是一个模块。

2-2 Python之导入模块


  1. 使用一个模块,必须先导入该模块,导入方式有2种
    import package_name
    from package import function_name1, function_name2
  2. 方式一import package_name,可以访问package_name中定义的所有公开函数、类和变量,通过package_name.function_name的方式调用,不会存在冲突。缺点:导入了package_name中所有的function,可能只会用到其中某几个;
  3. 方式二from package import function_name1,function_name2,仅将自己需要的function导入,可以直接调用function_name1或function_name2。缺点:直接使用函数名可能会存在函数名冲突,可以在导入时给函数起别名,如 from package import function_name1 as func。

2-3 Python之动态导入模块


  1. 若导入的模块不存在,Python解释器会报错ImportError
  2. 有时候两个不同的模块(或版本不同的两个模块),如StringIO和cStringIO都提供了StringIO这个功能,但StringIO是由纯Python代码编写的,而cStringIO的部分函数是用C语言编写的,运行速度会更快,利用’ImportError’可以动态地导入模块。
    try:
        from cStringIO import StringIO
    except ImportError:
        from StringIO import StringIO
    


2-4 Python之使用future


  1. 当Python新版本中增加了新特性时,会将该特性添加到Python旧版本的future模块中,以便旧代码在旧版本中测试新特性;
  2. 例子:Python2.x版本中,整数除法运算的结果仍为整数,但Python3.x版本中,整数除法的运算结果可以是浮点型数据,在Python2.x中导入3.x的新特性。
    from __future__ import division
    print (10 / 3)
    3.3333333333333335
    


2-5 Python之导入第三方模块


  1. Python提供了2种管理第三方模块的工具:easy_install 和 pip,后者是python官方推荐的管理工具,已经内置到了python中;
  2. 语句:pip install package_name可以通过pipy.python.org网站来根据关键字进行查找三方模块的名字。

2-6 Python之模块作用域


  1. 模块中会定义很多函数和变量,有些我们希望给别人调用,有些我们希望只在模块内部使用,Python中通过通过 _ 前缀来实现;
  2. 正常的函数和变量都是公开的(public),可以直接调用;
  3. 类似\_\_xxx__这样的变量是特殊变量,可以直接调用,但有特殊用途;
  4. 类似\_xxx\__xxx这样的变量或函数是非公开的(private),不应该直接被引用。不应该被直接引用而不是不能被直接引用,因为Python并没有一种方法可以完全限制访问private函数,只是从编程习惯上不应该引用private函数或变量;
  5. 外部需要调用的函数定义成public的,外部不需要调用的函数定义成private的。调用public函数而不必关心内部private函数的细节,这是一种非常有用的代码封装和抽象的方法。

第3章 面向对象编程基础

3-1 Python之面向对象编程


  1. 面向对象编程是一种程序设计范式,把程序看做不同对象的相互调用,是对现实世界建立的对象模型;
  2. 面向对象编程的基本思想:类和实例,类用于定义抽象类型,实例是根据类的定义创建的;
  3. 在Python中,用Class定义类,用类名和函数调用创建实例;

3-2 Python之创建类和实例


  1. Python中,用Class关键字来定义类,按编程习惯一般将类名首字母大写,紧接着是(object): 表示该类是从哪个类继承下来的;
  2. 创建实例,使用类名()类似函数调用的形式创建;
  3. 创建一个类Person和2个实例
    class Person(object):
    pass
    xiaoming = Person()
    xiaohong = Person()
    


3-3 Python创建实例属性


  1. 由于Python是动态语言,对每一个实例,都可以直接给他们的属性赋值,如:给xiaoming这个实例加上name/gender/birth属性;
    xiaoming = Person()
    xiaoming.name = ‘Xiao Ming’
    xiaoming.gender = ‘Male’
    xiaoming.birth = ‘1989-10-01’
    
  2. 同一个类的不同实例,可以添加不同的属性,如:给xiaohong加上name/school/grade属性;
    xiaohong = Person()
    xiaohong.name = ‘Xiao Hong’
    xiaohong.school = ’No.1 High School’
    xiaohong.grade = 2
    
  3. 除了上述的方法,还可以通过setattr(对象,属性,属性的值)对实例赋值,如:setatrr(xiaoming,gender,male),相当于xiaoming.gender = ‘Male’
  4. 实例的属性可以像普通的变量一样进行运算操作:xiaoming.age = xiaohong.age + 1

3-4 Python之初始化实例属性


  1. 类起到了模版的作用,在定义类的时候,可以通过特殊方法__init__把一些必须的实例属性直接加上,即初始化实例属性;
  2. __init__方法的第一个参数永远是self,表示创建的实例本身,在其内部,可以把各种属性绑定到self,但是在创建实例时,不能传入空参数,必须传入与__init__方法匹配的参数,除了self本身,Python解释器会自动把实例变量传进去;
    class Person(object):
        def __init__(self,name,gender,age)
            self.name = name
            self.gender = gender
            self.age = age
    
    xiaoming = Person(‘xiaoming’,‘Male’,18)
    
  3. 和普通函数相比,类中定义的函数的不同点:第一个参数永远是实例变量自身self,且调用时不必传入参数。

3-5 Python之实例属性的访问限制


  1. Python对属性权限的控制是通过属性名来实现的,不希望被外部访问的属性,在属性名前添加双下划线(__)即可,通常单下划线开头在子类中使用,双下划线开头不能在子类中使用。
    class Person(object):
        def __init__(self,name):
            self.name = name
            sefl.__age = age
    
    p = Person(‘Bob’)
    print (p.name)
    -- > Bob
    
    print (p.__age)
    Error
    Traceback (most recent call last):
    File "", line 1, in 
    AttributeError: 'Person' object has no attribute ‘__age'
    


3-6 Python之创建类属性


  1. 类是模版,而实例则是根据类创建的对象,绑定在一个实例上的属性不会影响其它实例的属性;
  2. 类本身也是对象,在类上绑定一个属性,则该类的所有实例都可以访问类属性。每个实例各自拥有相互独立的属性,而类属性有且共有一份;
  3. 在定义类时可以定义类属性,因为类属性是直接绑定在类上的,所以访问类属性不需要创建实例,可以直接访问;
    class Person(object):
        address = ‘Earth’
        def __init__(self,name)
            self.name = name
    
    print Person.address
    -- > 'Earth'
    
  4. Python是动态语言,类属性也可以动态地添加或修改,因为类属性只有一份,当类属性发生变化时所有实例访问的类属性也发生了变化。

3-7 Python之类和实例的属性名冲突


  1. 修改类属性会导致所有实例访问到的类属性发生改变,若在实例中直接对类属性进行赋值,则该实例会增加一个和类属性同名的属性,但是类属性不会发生变化,当实例属性与类属性同名时,实例属性优先级高,会屏蔽掉该实例对类属性的访问;
  2. 尽量不要在实例中修改类属性,因为它实际上并没有修改类属性,而只是给该实例绑定了一个实例属性。

3-8 Python之定义实例方法


  1. 在类的内部,除了可以定义实例的属性,还可以定义实例的方法(函数);
  2. 类中定义的实例方法就是定义的函数,其第一个参数永远是self,指向调用该方法的实例本身,其它的参数和普通函数的参数一样。带下划线的__init__可以看作是一个特殊的实例方法;
  3. 实例方法必须在实例上调用,在实例方法内部,可以访问所有的实例属性,若外部需要访问私有属性,可以通过方法调用获得,这种数据封装的形式,除了能保护内部数据一致性外,还可以简化外部调用的难度。

3-9 Python之类中方法也是属性


  1. 在类中定义的实例方法是一个绑定在实例上的函数对象,只能在实例上调用,其实也是实例的属性,可以动态地为实例添加;
  2. 借助types.MethodType()可以将外部定义的函数变为类中的实例方法,语法:types.MethonTypes(外部定义的函数名,实例名,类名)
    import types
    #外部定义的函数
    def fn_get_grade(self):
        if self.score >= 80:
            return ‘A’
        if self.score >= 60:
            return ‘B’
        else:
            return ‘C’
    
    class Person(object):
        def __init__(self,name,score):
            self.name = name
            self.score = score
    
    p1 = Person(‘Bob’,90)
    p1.get_grade() = types.Method.Types(fn_get_grade,p1,Person)
    print (p1.get_grade())
    -- > A
    
  3. 给一个实例动态地添加方法并不常见,直接在class中定义更直观。

3-10 Python之类方法


  1. 和属性类似,方法也分实例方法和类方法,在Class中定义的全部是实例方法,实例方法的第一个参数是self,即实例变量自身;
  2. 通过标记一个@classmethod将定义的方法绑定到类上,而非实例方法,类方法的第一个参数将传入类本身,通常将参数名命名为cls
  3. 因为是在类上调用,类方法无法获得任何实例变量,只能获得类的引用。
    class Person(object):
        count = 0
        @classmethod
        def how_many(cls):
            return cls.count  #cls.count相当于Person.count
        def __init__(self, name):
            self.name = name
            Person.count = Person.count + 1
    
    print (Person.how_many())
    -- > 0 
    
    p1 = Person('Bob')
    print Person.how_many()
    -- > 1
    


第4章 类的继承

4-1 Python之什么是继承


  1. 定义一个新类时,不必从头编写,可以从现有的类中继承,获得现有类的所有功能,新类只需要编写现有类缺少的功能;
  2. 被继承的类称为:父类、基类、超类;新建的类称为:子类、派生类、继承类;
  3. 继承的优点:1)复用现有的代码;2)自动拥有了现有类的所有功能;3)只需要编写缺少的新功能;
  4. 继承可以一级一级地继承下去,而任何类,最终都可以追溯到根类object,这些继承关系看上去像一颗倒着的树。

4-2 Python之继承一个类


  1. 继承的特点:1)总是从某个类继承,若没有合适的类,默认从object类继承;2)调用super().__init__来初始化父类的功能,否则父类的属性可能没有被正确调用;
    # 定义一个类Person
    class Person(object):
        def __init__(self, name, gender):
            self.name = name
            self.gender = gender
    
    # 从上述类继承一个子类Student,增加新属性score
    class Student(Person):
        def __init__(self, name, gender, score)
            super(Student,self).__init__(name, gender)
            self.score = score
    
  2. super(Student, self)返回当前类继承的父类,即Person类,然后调用__init__方法,初始化Person类,否则子类Student会没有属性name和gender。注意:self已在super()中传入,在__init__中将隐形传递,不需要写出来,也不能写出来。

4-3 Python之判断类型


  1. isinstance():判断一个变量的类型,如Python内置的数据类型str/int/list/dict等,也可以判断我们定义的类,它们本质上都是数据对象;
  2. isinstance(实例,类):判断指定的实例是否属于指定的类;
  3. 在继承链上,一个父类的实例不能是子类类型,因为子类比父类多了一些属性和方法;一个子类的实例既可以看成本身的类型,也可以看成父类的类型。

4-4 Python之多态


  1. 类具有继承关系,子类类型可以向上看作是父类类型;
  2. 多态的几层意思:
  • 父类的方法(如run()),只要是父类或其子类,就会自动调用该方法,运行时作用于传入的对象(该父类或其子类);
  • 子类和父类拥有相同的方法(如run())时,我们认为子类的方法run()覆盖了父类的方法run(),运行代码时总会调用子类的run();
  • 子类调用方法(如run())时,总是先查找该子类自身的定义,若没有定义方法(如run()),则顺着继承链向上查找,直到在某个父类中找到该方法。
  1. 开闭原则:动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用
  • 对扩展开放:允许增加新子类
  • 对修改封闭:不需要修改父类的方法
  1. 有了继承,才有了多态,在调用类实例方法时,尽量把变量当作父类,以便所有子类类型都可以正常接收。

4-5 Python之多重继承


  1. 除了从一个父类继承外, Python允许从多个父类继承,即称为多重继承;
  2. 多重继承的目的:从两种继承树中分别选择并继承出子类,以便组合功能使用;
  3. 多重继承的继承链不再是一棵继承树了,如图,D同时继承了B和C,D便拥有了A、B、C的全部功能;
    【廖雪峰课程笔记】之Python进阶_第1张图片
  4. 多重继承通过super().__init__()方法调用时,A虽然被继承了2次,但__init__只调用一次。

4-6 Python之获取对象信息的函数


  1. isinstance():判断一个变量是哪种实例类型;
  2. type():获取变量的类型,返回一个Type对象;
  3. dir():获取变量的所有属性,包括带下划线的特殊属性;
  4. setattr():为变量设置新的属性,如:setattr(s, ’name’, ‘Bob’) 表示为变量s的属性name设置值为Bob;
  5. getattr():获取变量指定属性的值,属性不存在时会报错,如:getattr(s, ’name’) = Bob表示获取变量s的属性name的值;
  6. hasattr():判断变量是否有指定的属性,如:hasattr(s, ’name’) = Ture

第5章 定制类

5-1 Python之什么是特殊方法


  1. 特殊方法在Python中,又称为魔术方法,是定义在类中的,不需要直接调用,Python的某些函数或操作符会调用对应的特殊方法;
  2. 使用时,我们只需要编写用到的特殊方法,若有关联性的特殊方法也都需要编写。

5-2 Python之strrepr


  1. 定义一个类
    class Student(object):
        def __init__.(self, name, grade)
            self.name = name
            self.grade = grade
    
  2. 创建一个实例,print(s1)或直接输入变量s1,结果返回的均是实例的地址
    s1 = Student(‘Bob’,16)
    print (s1)
    <__main__.Student object at 0x7f7a151ff450>
    
  3. 定义类时,调用__str__特殊方法
    class Student(object):
        def __init__.(self, name, grade)
            self.name = name
            self.grade = grade
        def __str__.(self):
            return ‘(Student: %s, %s)’ % (self.name, self.grade)
    
  4. 创建一个实例,print (s2)时返回的是实例;直接输入变量s2时返回的是实例的地址。两者不同的原因:print变量,调用的是__str__(),返回用户看到的字符,用于显示给用户;直接输入变量调用的是__repr__(),返回程序开发者看到的字符串,用于调试服务;
    s2 = Student(‘Bob’, 17)
    print (s2)
    -- > (Student:Bob, 17)
    
    s2
    <__main__.Student object at 0x7f7a151ff450>
    
  5. 上述4中的问题,有一个偷懒的做法是再定义一个repr(),并赋值给str()即可。
    class Student(object):
        def __init__.(self, name, grade)
            self.name = name
            self.grade = grade
        def __str__.(self):
            return ‘(Student: %s, %s)’ % (self.name, self.grade)
        __repr__ = __str__
    


5-3 Python之特殊方法cmp


  1. 使用特殊方法__cmp__对一组类的实例进行排序,__cmp__用实例自身self和传入的实例进行比较,若self应排在前面则返回-1;若self应排在后面则返回1;若两者相等则返回0;
    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__
    
        def __cmp__(self, s):
            if self.name < s.name:
                return -1
            if self.name > s.name
                return 1
            else:
                return 0
    
    L = [Person(‘Bob’,’male’), Person(‘Xz’,’female’),Person(’ShiMei’,’male’)]
    
    print (sorted(L))
    -- > [(Bob:male),(Xz:female),(ShiMei:male)]            
    
  2. 示例:使用特殊方法将Student类中实例,按分数从高到低排序,分数相同的按名字排序。
    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 self.score == s.score:
                return cmp(self.name,s.name)
            return -cmp(self.score,s.score)
    
    L = [Student('Tim', 99), Student('Bob', 88), Student('Alice', 99)]
    print (sorted(L))
    -- > [(Alice: 99), (Tim: 99), (Bob: 88)]
    


5-4 Python之特殊方法len


  1. 如果一个类表现得像list,要获得有多少个元素,需要用len函数,类必须提供一个特殊方法len()返回类的元素个数;
  2. 示例:编写一个Fib类,Fib(10)表示斐波那契数列前10个元素,求元素个数。
    class Fib(object):  #定义类Fib
        def __init__(self, num):   #为实例添加了属性:num,但在内部并没有进行self绑定
            a, b, L = 0, 1, []
            for n in range(num):   #变量num只是起到了遍历次数的作用,是从外部传入的参数
                L.append(a)
                a, b = b, a + b  #遍历后总是将前2项相加,作为第三项    
            self.numbers = L   #将遍历的a值添加到列表L后,绑定到实例的属性
    
        def __str__(self):  #通过特殊方法返回实例的结果,而非对象
            return str(self.numbers)  #通过str函数将结果转换成字符串
    
        _repr__ = __str__  #将特殊方法__str__赋值给__repr__,便于非  print情况下,直接能返回实例结果
    
        def __len__(self):   #通过特殊方法返回元素个数
            return len(self.numbers)
    
    f = Fib(10)
    print (f)
    -- > [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    
    print len(f)
    -- > 10
    


5-5 Python之数学运算


  1. Python的四则运算不仅局限于int、float等数据类型,还可以是有理数、矩阵等;
  2. 定义一个Rational类表示有理数,p、q都是整数,用 p / q表示有理数
    class Rational(object):
        def __init__(self, p, q):
            self.p = p
            self.q = q
        def __str__(self):
            return ‘%s / %s’ % self.p / self.q
    
  3. 加减乘除四则运算的特殊方法:__add__ / __sub__ / __mul__ / __div__
  4. 示范:运算结果是6/8,显示的时候需要归约到最简形式3/4
    def gcd(a, b):
        if b == 0:
            return a
        return gcd(b, a % b)
    
    class Rational(object):
        def __init__(self, p, q):
            self.p = p
            self.q = q
        def __add__(self, r):
            return Rational(self.p * r.q + self.q * r.p, self.q * r.q)
        def __sub__(self, r):
            return Rational(self.p * r.q - self.q * r.p, self.q * r.q)
        def __mul__(self, r):
            return Rational(self.p * r.p, self.q * r.q)
        def __div__(self, r):
            return Rational(self.p * r.q, self.q * r.p)
        def __str__(self):
            g = gcd(self.p, self.q)
            return '%s/%s' % (self.p / g, self.q / g)
        __repr__ = __str__
    
    r1 = Rational(1, 2)
    r2 = Rational(1, 4)
    print (r1 + r2)
    -- > 3/4
    print (r1 - r2)
    -- > 1/4
    print (r1 * r2)
    -- > 1/8
    print (r1 / r2)
    -- > 2/1
    


5-6 Python之数据类型转换


  1. 在类中进行数据类型的转换,需要调用对应的特殊方法,如__int__ / __float__等。
    class Rational(object):
        def __init__(self, p, q):
            self.p = p
            self.q = q
        def __int__(self):
            return self.p // self.q
        def __float__(self):
            return float(self.p) / self.q
    
    print (int(Rational(7, 2)))
    -- > 3
    print (float(Rational(10, 3))) 
    -- > 3.33333333333
    


5-7 Python之@property


  1. 在绑定属性时,直接给属性赋值无法检查所赋值的有效性,Python内置的@property装饰器可以把一个方法变为属性调用;
  2. 示例:属性score进行了set和get定义,是可读写属性;属性grade进行了set定义,是只读属性。
    class Student(object):
        def __init__(self, name, score):
            self.name = name
            self.__score = score
    
        @property  #将定义的方法fenshu装饰成属性调用
        def fenshu(self):  #定义方法fenshu,返回属性score结果,相当于get()函数
            return self.__score
    
        @fenshu.setter  #是@property装饰后的副产品,将定义的方法score装饰成属性调用
        def score(self, score):   #定义方法score进行属性参数判断,相当于set()函数
            if score < 0 or score > 100:
                raise ValueError('invalid score')
            self.__score = score
    
        @property  #将定义的方法grade装饰成属性调用
        def grade(self):  #定义方法grade进行属性参数判断,相当于set()函数
            if self.score < 60:
                return 'C'
            if self.score < 80:
                return 'B'
            return 'A'
    
    s = Student('Bob', 59)
    print (s.grade)
    -- > C
    
    s.score = 60
    print (s.grade)
    -- > B
    
    s.score = 99
    print (s.grade)
    -- > A
    


5-8 Python之特殊方法slots


  1. Python是动态语言,任何实例在运行期间都可以动态地增加属性,通过特殊方法__slots__可以指定实例的属性,限制增加新的属性,本质上就是一个类允许的属性列表;
  2. 使用特殊方法__slots__时,可以节省内存,但该方法仅对当前类起作用,对继承的子类不起作用,除非子类也使用了__slots__,这样,子类允许定义的属性就是自身的__slots__加上父类的__slots__

5-9 Python之特殊方法call


  1. 在Python中,所有的函数都是可调用对象,通过特殊方法__call__可以将一个类实例变成可调用对象;
  2. 示例一:在特殊方法__call__中,friend是传参
    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
    
    p = Person('Bob','male')
    p('Tim’)
    -- > My name is Bob...
    -- > My friend is Tim...
    
  3. 示例二:改进6-4中斐波那契数列
    class Fib(object):
        def __call__(self, num):
            a, b, L = 0, 1, []
            for n in range(num):
                L.append(a)
                a, b = b, a + b
            return L
    
    f = Fib()
    print (f(10))
    -- > [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
    


5-10 Python之特殊方法iter


  1. 如果需要对一个类进行for循环遍历,类似list/tuple,需要使用特殊方法__iter__,该方法返回一个迭代对象,Python的for循环会不断调用该迭代对象的next()方法,返回循环的下一个值;
  2. 示例:以斐波那契数列为例,写一个Fib类,可以作用于for循环。
    class 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 > 200:   #退出循环的条件
                    raise StopIteration();
            return self.a  #返回下一个值
    
    for n in Fib():            #进行for循环遍历
        print (n)
    1
    1
    2
    3
    5
    8
    13
    21
    34
    55
    89
    144
    


5-11 Python之类切片


  1. 通过特殊方法__iter__可以对类进行遍历,类似list/tuple,但要想进行切片操作获取数据,需要使用特殊方法__getitem__实现,通常含2个参数,第一个是实例本身self,第二个传入的是一个int值表示指定的索引位置或者一个切片对象slice表示索引列表,因此,需要对第二参数进行if判断;
    class Fib(object):
        def __getitem__(self, n):
            if isinstance(n, int):
                a, b = 0, 1
                for x in range(n):
                    a, b = b, a + b
                return a
            if isinstance(n, slice):
                start = n.start
                stop = n.stop
                a, b = 0, 1
                L = []
                for x in range(stop):
                    if x >= start:
                        L.append(a)
                    a, b = b, a + b
                return L
    
    f = Fib()[5]
    print (f)
    -- > 5
    
    f = Fib()[0:5]
    print (f)
    -- > [0, 1, 1, 2, 3]
    
  2. 上述没有对切片slice为负数情况和步长step进行处理,要实现一个正确的__getitem__还需要做很多处理。类似的,还有特殊方法__setitem__把对象视为list或dict来对集合赋值、特殊方法__delitem__用于删除某个元素。总之,我们定义的类,表现得和Python自带的list/tuple/dict等没什么区别,这完全归功于动态语言的鸭子类型,不需要强制继承某个接口。

你可能感兴趣的:(【廖雪峰课程笔记】之Python进阶)