Python进阶

目录

一、函数式编程

1.python中的函数式编程

2.高阶函数

2.1 map()

2.2 reduce()

2.3 filter()

2.4 sorted()

2.5 函数作为返回值

3. 闭包

4. 匿名函数

5. 装饰器 

5.1 python中编写无参数decorator:2层嵌套

5.2 python中编写带参数decorator:再在最外面添加1层嵌套,3层嵌套

5.3 python中完善decorator:__name__、__doc__属性改变问题

6. 偏函数

二、模块

1. 模块和包

2. __future__

三、面向对象编程基础

1. 面向对象编程的基本思想

2. 实例属性,及其初始化

3. 类属性

4. python中类属性和实例属性名字冲突怎么办

5. python中访问限制

6. 实例方法

6.1 定义实例方法

6.2 给一个实例动态添加方法

7. 类方法

四、类的继承

1. 继承概念

2. 继承一个类

3. 判断类型

4. 多态

5. 多重继承

6. 获取对象信息:type()、 getattr() 和 setattr( )

五、定制类

1. 特殊方法 / 魔术方法

1.1 特殊方法

1.2 特殊方法有哪些 

1.3 如何正确实现特殊方法

2. python中 __str__和__repr__

3. python中 __cmp__

4. python中 __len__


一、函数式编程

1.python中的函数式编程

  • 函数:function
  • 函数式:functional,一种编程范式,一种抽象编程计算的编程模式。函数——函数式,好比于,计算——计算机

不同语言的抽象层次不同:

                                                   

 函数式编程的特点:

        

Python支持的函数式编程:

       

2.高阶函数

高阶函数:能接受函数做参数的函数

          

def add(x,y,f):
    return f(x)+f(y)

add(-5,4,abs)    # 14

Python内置的高阶函数:map、reduce、filter

2.1 map()

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

2.2 reduce()

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

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

reduce()还可以接收第3个可选参数,作为计算的初始值。如果把初始值设为100,计算:

          reduce(f, [1, 3, 5, 7, 9], 100)

结果将变为125,因为第一轮计算是计算初始值和第一个元素:f(100, 1),结果为101

2.3 filter()

filter()函数是 Python 内置的另一个有用的高阶函数,filter()函数接收一个函数 f 和一个list,这个函数 f 的作用是对每个元素进行判断,返回 True或 False,filter()根据判断结果自动过滤掉不符合条件的元素,返回由符合条件元素组成的新list。

例子:

利用filter()过滤出1~100中平方根是整数的数,即结果应该是:[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

import math

def is_sqr(x):
    return int(math.sqrt(x))*int(math.sqrt(x))==x

print filter(is_sqr, range(1, 101))

2.4 sorted()

sorted 语法:

sorted(iterable, cmp=None, key=None, reverse=False)

参数说明:

  • iterable -- 可迭代对象。
  • cmp -- 比较的函数,这个具有两个参数,参数的值都是从可迭代对象中取出,此函数必须遵守的规则为,大于则返回1,小于则返回-1,等于则返回0。
  • key -- 主要是用来进行比较的元素,只有一个参数,具体的函数的参数就是取自于可迭代对象中,指定可迭代对象中的一个元素来进行排序。
  • reverse -- 排序规则,reverse = True 降序 , reverse = False 升序(默认)。

参数cmp和key可相互替换

       L2 = sorted(L1, key=lambda x:x.name)
       L2 = sorted(L1, cmp=lambda x,y:cmp(x.name, y.name))

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

例子:倒序排序

def reversed_cmp(x, y):
    if x > y:
        return -1
    if x < y:
        return 1
    return 0

>>> sorted([36, 5, 12, 9, 21], reversed_cmp)
[36, 21, 12, 9, 5]

请利用sorted()高阶函数,对字符串排序,实现忽略大小写排序的算法:

def cmp_ignore_case(s1, s2):
    s1=s1.lower()
    s2=s2.lower()
    if s1>s2:
        return 1
    if s1<s2:
        return -1
    return 0

sorted(['bob', 'about', 'Zoo', 'Credit'], cmp_ignore_case)   # ['about', 'bob', 'Credit', 'Zoo']

2.5 函数作为返回值

高阶函数除了可以接受函数作为参数外,还可以把函数作为结果值返回。

def f():
    print 'call f()...'
    def g():
        print 'call g()...'  
    return g

>>> x = f()   # 调用f()
call f()...

# (1)变量x是f()返回的函数:
>>> x     
<function g at 0x1037bf320>

# (2)x()是对返回的函数进行调用
>>> x()   
call g()...   # 调用x()就是执行g()函数定义的代码

例子: 

def lazy_sum(*args):
    def sum():
        ax = 0
        for n in args:
            ax = ax + n
        return ax
    return sum

f = lazy_sum(1, 3, 5, 7, 9)

>>> f
<function lazy_sum.<locals>.sum at 0x101c6ed90>

>>> f()
25

在这个例子中,我们在函数lazy_sum中又定义了函数sum,并且,内部函数sum可以引用外部函数lazy_sum的参数和局部变量,当lazy_sum返回函数sum时,相关参数和变量都保存在返回的函数中,这种称为“闭包(Closure)”的程序结构拥有极大的威力。

请再注意一点,当我们调用lazy_sum()时,每次调用都会返回一个新的函数sum,即使传入相同的参数:
>>> f1 = lazy_sum(1, 3, 5, 7, 9)
>>> f2 = lazy_sum(1, 3, 5, 7, 9)
>>> f1==f2
False

例子:请编写一个函数calc_prod(lst),它接收一个list,返回一个函数,返回函数可以计算参数的乘积:

from functools import reduce
def calc_prod(lst):
    def lazy_prod():
        def f(x,y):
            return x*y
        return reduce(f, lst)
    return lazy_prod

f = calc_prod([1, 2, 3, 4])
print f()    # 24

3. 闭包

在函数内部定义的函数和外部定义的函数是一样的,只是他们无法被外部访问:

def g():
    print 'g()...'

def f():
    print 'f()...'
    return g

 g 的定义移入函数 f 内部,防止其他代码调用 g

def f():
    print 'f()...'
    def g():
        print 'g()...'
    return g

但是,考察上一小节定义的 calc_sum 函数:

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

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

像这种内层函数引用了外层函数的变量(参数也算变量),然后返回内层函数的情况,称为闭包(Closure)。所以,闭包用起来简单,实现起来可不容易。另一个需要注意的问题是,返回的函数并没有立刻执行,而是直到调用了f()才执行。

闭包的特点是返回的函数还引用了外层函数的局部变量,所以,要正确使用闭包,就要确保引用的局部变量在函数返回后不能变。举例如下:

# 希望一次返回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(),f2()和f3()结果应该是1,4,9,但实际结果全部都是 9。

原因就是当count()函数返回了3个函数时,代码for i in range(1, 4)已经在count()内循环遍历执行,即这3个函数所引用的变量 i 的值已经变成了3。由于f1、f2、f3并没有被调用,所以,此时他们并未计算 i*i,当 f1 被调用时,即def f()被调用,执行3*3:

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

因此,返回函数不要引用任何循环变量,或者后续会发生变化的变量。

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

def count():
    def f(j):
        def g():
            return j*j
        return g
    fs = []
    for i in range(1, 4):
        fs.append(f(i)) # f(i)立刻被执行,因此i的当前值被传入f()
    return fs

4. 匿名函数

匿名函数: lambda x: ??? 

如:sorted([1, 3, 9, 5, 0], lambda x,y: -cmp(x,y))    结果为:[9, 5, 3, 1, 0]

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

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

def is_not_empty(s):
    return s and len(s.strip()) > 0
filter(is_not_empty, ['test', None, '', 'str', '  ', 'END'])

简化为:
filter(lambda x: True if x and len(x.strip())>0 else False, ['test', None, '', 'str', '  ', 'END'])

5.装饰器 

在代码运行期间动态增加功能的方式,称之为“装饰器”(Decorator)。

示例:

使用高阶函数实现:

              

通过高阶函数返回一个新的函数

                

Python内置的@语法: 

                         

装饰器的作用:

                  

5.1 python中编写无参数decorator:2层嵌套

Python的 decorator 本质上就是一个高阶函数,它接收一个函数作为参数,然后,返回一个新函数。使用 decorator 用Python提供的 @ 语法,这样可以避免手动编写 f = decorate(f) 这样的代码。要让 @log 自适应任何参数定义的函数,可以利用Python的 *args 和 **kw,保证任意个数的参数总是能正常调用:

def log(f):
    def wrapper(*args, **kw):
        print 'call ' + f.__name__ + '()...'
        return f(*args, **kw)
    return wrapper

@log
def now():
    print('2015-3-25')

@log放到now()函数的定义处,相当于执行了语句:

now = log(now)

现在,对于任意函数,@log 都能正常工作。

编写一个@performance,它可以打印出函数调用的时间:

import time

def performance(f):
    def print_time(*args, **kw):
        print 'call '+f.__name__+'()'+time.strftime("%Y-%m-%d:%H:%M:%S", time.localtime())+'...'
        return f(*args, **kw)
    return print_time

@performance
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))

print factorial(10)

5.2 python中编写带参数decorator:再在最外面添加1层嵌套,3层嵌套

考察上一节的 @log 装饰器,发现对于被装饰的函数,log打印的语句是不能变的(除了函数名)。

如果有的函数非常重要,希望打印出'[INFO] call xxx()...',有的函数不太重要,希望打印出'[DEBUG] call xxx()...',这时,log函数本身就需要传入'INFO'或'DEBUG'这样的参数,类似这样:

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

@log('DEBUG')
def now():
    print('2015-3-25')

和两层嵌套的decorator相比,3层嵌套的效果是这样的:

>>> now = log('DEBUG')(now)

把上面的定义翻译成高阶函数的调用,就是:my_func = log('DEBUG')(my_func)

例子:上一节的@performance只能打印秒,请给 @performace 增加一个参数,允许传入's'或'ms'

import time

def performance(predix):
    def log_per(f):
        def wrapper(*args, **kw):
            t1 = time.time()
            r = f(*args, **kw)
            t2 = time.time()
            s = (t2-t1)*1000 if predix=='ms' else (t2-t1)
            
            print 'call '+f.__name__+'()'+str(s) + '...'
            
            return r
        return wrapper
    return log_per

@performance('ms')
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))

print factorial(10)

5.3 python中完善decorator:__name__、__doc__属性改变问题

@decorator可以动态实现函数功能的增加,但是,经过@decorator“改造”后的函数,和原函数相比,decorator返回的新函数函数名已经不是'factorial',而是@log内部定义的'wrapper'。这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__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

这样写decorator很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上,所以Python内置的functools.wraps,可以用来自动化完成这个“复制”的任务:

import functools
def log(f):
    @functools.wraps(f)
    def wrapper(*args, **kw):
        print 'call...'
        return f(*args, **kw)
    return wrapper

例子:

import time, functools

def performance(predix):
    def log_per(f):
        @functools.wraps(f)
        def wrapper(*args, **kw):
            t1 = time.time()
            r = f(*args, **kw)
            t2 = time.time()
            s = (t2-t1)*1000 if predix=='ms' else (t2-t1)
            
            print 'call '+f.__name__+'()'+str(s) + '...'
            
            return r
        return wrapper 
    return log_per

@performance('ms')
def factorial(n):
    return reduce(lambda x,y: x*y, range(1, n+1))

print factorial.__name__     # factorial

6. 偏函数

functools.partial 帮助我们创建一个偏函数,不需要我们自己定义int2(),可以直接使用下面的代码创建一个新的函数int2:

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

所以,functools.partial可以把一个参数多的函数变成一个参数少的新函数,少的参数需要在创建时指定默认值,这样,新函数调用的难度就降低了。

import functools

# sorted_ignore_case = functools.partial(sorted, key=str.lower)
# sorted_ignore_case = functools.partial(sorted, key=lambda x:x.lower())
# sorted_ignore_case = functools.partial(sorted, cmp=lambda x,y:cmp(x.lower(),y.lower()))
sorted_ignore_case = functools.partial(sorted, key=str.capitalize)

print sorted_ignore_case(['bob', 'about', 'Zoo', 'Credit'])

二、模块

1. 模块和包

模块:当代码量过多时

包:

引用完整模块:

在文件系统中:

                                                

如何区分包和普通目录:

2. __future__

Python的新版本会引入新的功能,把下一个新版本的特性导入到当前版本,但是,实际上这些功能在上一个老版本中就已经存在了。要“试用”某一新的特性,就可以通过导入__future__模块的某些功能来实现。如要在Python 2.7中引入3.x的除法规则,导入__future__division

from __future__ import division

当新版本的一个特性与旧版本不兼容时,该特性将会在旧版本中添加到__future__中,以便旧的代码能在旧版本中测试新特性。

三、面向对象编程基础

1. 面向对象编程的基本思想

            

                

数据封装:对每一个实例的属性做封装

                       

2. 实例属性,及其初始化

在定义 Person 类时,可以为类添加一个特殊的__init__()方法,当创建实例时,__init__()方法被自动调用,我们就能在此为每个实例都统一加上属性,被赋予不同的属性值。

关键字属性:

class Person(object):
    def __init__(self, name, gender, birth, **kw):
        self.name = name
        self.gender = gender
        self.birth = birth
        # 关键字参数
        # 方式1:
        # self.__dict__.update(kw)   
        # 方式2:
        for k, v in kw.iteritems():
            setattr(self, k, v)

xiaoming = Person('Xiao Ming', 'Male', '1990-1-1', job='Student')

3. 类属性

类是模板,而实例则是根据类创建的对象。绑定在一个实例上的属性不会影响其他实例,但是,类本身也是一个对象,如果在类上绑定一个属性,则所有实例都可以访问类的属性,并且,所有实例访问的类属性都是同一个!

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

class Person(object):
    address = 'Earth'
    def __init__(self, name):
        self.name = name
  • 实例属性:每个实例各自拥有,互相独立;只能通过实例访问。
  • 类属性:有且只有一份,所有实例共享;可通过类名访问,也可通过实例访问。

因为类属性是直接绑定在类上的,所以,访问类属性不需要创建实例,就可以直接访问:

print Person.address
# => Earth

对一个实例调用类的属性也是可以访问的,所有实例都可以访问到它所属的类的属性:

p1 = Person('Bob')
p2 = Person('Alice')
print p1.address
# => Earth
print p2.address
# => Earth

由于Python是动态语言,类属性也是可以动态添加和修改的:

Person.address = 'China'
print p1.address
# => 'China'
print p2.address
# => 'China'

因为类属性只有一份,所以,当Person类的address改变时,所有实例访问到的类属性都改变了。

例子:给 Person 类添加一个类属性 count,每创建一个实例,count 属性就加 1,这样就可以统计出一共创建了多少个 Person 的实例。

class Person(object):
    count = 0
    def __init__(self, name):
        self.name=name
        Person.count=Person.count+1

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

p2 = Person('Alice')
print Person.count

p3 = Person('Tim')
print Person.count
 

4. python中类属性和实例属性名字冲突怎么办

注意:在实例上修改类属性,它实际上并没有修改类属性,而是给实例绑定了一个实例属性

修改类属性会导致所有实例访问到的类属性全部都受影响,但是,如果在实例变量上修改类属性会发生什么问题呢?

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

p1 = Person('Bob')
p2 = Person('Alice')

print 'Person.address = ' + Person.address

p1.address = 'China'
print 'p1.address = ' + p1.address

print 'Person.address = ' + Person.address
print 'p2.address = ' + p2.address

结果如下:

Person.address = Earth
p1.address = China
Person.address = Earth
p2.address = Earth

我们发现,在设置了 p1.address = 'China' 后,p1访问 address 确实变成了 'China',但是,Person.address和p2.address仍然是'Earch',怎么回事?

原因是 p1.address = 'China'并没有改变 Person 的 address,而是给 p1这个实例绑定了实例属性address ,对p1来说,它有一个实例属性address(值是'China'),而它所属的类Person也有一个类属性address,所以:

  • 访问 p1.address ,优先查找实例属性,返回'China'。
  • 访问 p2.address ,p2没有实例属性address,但是有类属性address,因此返回'Earth'。

可见,当实例属性和类属性重名时,实例属性优先级高,它将屏蔽掉对类属性的访问

当我们把 p1 的 address 实例属性删除后,访问 p1.address 就又返回类属性的值 'Earth'了:

可见,千万不要在实例上修改类属性,它实际上并没有修改类属性,而是给实例绑定了一个实例属性

5.python中访问限制

我们可以给一个实例绑定很多属性,如果有些属性不希望被外部访问到怎么办?Python对属性权限的控制是通过属性名来实现的,如果一个属性由双下划线开头(__),该属性就无法被外部访问

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

以单下划线开头的属性"_xxx"虽然也可以被外部访问,但是,按照习惯,他们不应该被外部访问

class Person(object):
    def __init__(self, name):
        self.name = name
        self._title = 'Mr'
        self.__job = 'Student'
p = Person('Bob')
print p.name
# => Bob

print p._title
# => Mr

print p.__job
# => Error
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Person' object has no attribute '__job'

 把 Person 类属性 count 改为 __count,再试试能否从实例和类访问该属性:

class Person(object):

    __count = 0

    def __init__(self, name):
        self.name=name
        Person.__count  += 1
        print Person.__count

p1 = Person('Bob')
p2 = Person('Alice')

try:
    print Person.__count
except AttributeError:
    print 'attributeerror'

结果为:

1
2
attributeerror

6. 实例方法

class中定义的全部是实例方法,实例方法第一个参数 self 是实例本身。

6.1 定义实例方法

一个实例的私有属性就是以__开头的属性,无法被外部访问,那这些属性定义有什么用?

虽然私有属性无法从外部访问,但是,从类的内部是可以访问的。除了可以定义实例的属性外,还可以定义实例的方法。

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

class Person(object):

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

    def get_name(self):
        return self.__name

get_name(self) 就是一个实例方法,它的第一个参数是self。__init__(self, name)其实也可看做是一个特殊的实例方法。

在实例方法内部,可以访问所有实例属性,这样,如果外部需要访问私有属性,可以通过方法调用获得,这种数据封装的形式除了能保护内部数据一致性外,还可以简化外部调用的难度

例子:给 Person 类增加一个私有属性 __score,表示分数,再增加一个实例方法 get_grade(),能根据 __score 的值分别返回 A-优秀, B-及格, C-不及格三档

# coding=utf-8
class Person(object):

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

    def get_grade(self):
        if self.__score >= 90:
            return 'A-优秀'
        elif self.__score>=60:
            return 'B-及格'
        else:
            return 'C-不及格'

p1 = Person('Bob', 90)
p2 = Person('Alice', 65)
p3 = Person('Tim', 48)

print p1.get_grade()
print p2.get_grade()
print p3.get_grade()

6.2 给一个实例动态添加方法

我们在 class 中定义的实例方法其实也是属性,它实际上是一个函数对象(属性、方法)

p1.get_grade 返回的是一个函数对象,但这个函数是一个绑定到实例的函数,p1.get_grade() 才是方法调用。

因为方法也是一个属性,所以,它也可以动态地添加到实例上,只是需要用 types.MethodType() 把一个函数变为一个方法:

import types
def fn_get_grade(self):
    if self.score >= 80:
        return 'A'
    if self.score >= 60:
        return 'B'
    return 'C'

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

p1 = Person('Bob', 90)
p1.get_grade = types.MethodType(fn_get_grade, p1, Person)
print p1.get_grade()
# => A

p2 = Person('Alice', 65)
print p2.get_grade()
# ERROR: AttributeError: 'Person' object has no attribute 'get_grade'
# 因为p2实例并没有绑定get_grade

给一个实例动态添加方法并不常见,直接在class中定义要更直观。

匿名函数定义的实例方法:

class Person(object):

    def __init__(self, name, score):
        self.name = name
        self.score = score
        self.get_grade = lambda: 'A'   # 匿名函数

p1 = Person('Bob', 90)

print p1.get_grade     # at 0x7f72e084b5f0>
print p1.get_grade()   # A

7. 类方法

通过标记一个 @classmethod,该方法将绑定到 Person 类上,而非类的实例。类方法的第一个参数将传入类本身,通常将参数名命名为 cls,上面的 cls.count 实际上相当于 Person.count

要在class中定义类方法,需要这么写:

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()

因为是在类上调用,而非实例上调用,因此类方法无法获得任何实例变量,只能获得类的引用。

例子:如果将类属性 count 改为私有属性__count,则外部无法读取,但可以通过一个类方法获取,请编写类方法获得__count值。

class Person(object):

    __count = 0

    # 类方法
    @classmethod
    def how_many(cls):
        return cls.__count
        
    def __init__(self,name):
        self.name=name
        Person.__count += 1
        
    # 实例方法:也可以访问类属性
    def how_many2(self):
        return Person.__count

print Person.how_many()    # 0

p1 = Person('Bob')
print Person.how_many()    # 1
print p1.how_many2()       # 1

四、类的继承

1. 继承概念

什么是继承,继承的好处:

  • 新类不必从头编写:复用已有代码
  • 新类从现有的类继承:自动拥有了现有类的所有功能
  • 新类只需要编写现有类缺少的新功能

父类和子类:

                                  

                                       

继承的特点:

                             

  • 总是从某个类继承,没有的话则继承object;
  • 不要忘记调用super().__init__():

            super().__init__()用来初始化父类,如果忘记写,父类的属性无法正确初始化

 

             

2. 继承一个类

如果已经定义了Person类,需要定义新的StudentTeacher类时,可以直接从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
  • 一定要用 super(Student, self).__init__(name, gender) 去初始化父类,否则,继承自 Person 的 Student 将没有 name 和 gender

  • 函数super(Student, self)将返回当前类继承的父类,即 Person ,然后调用__init__()方法,注意self参数已在super()中传入,在__init__()中将隐式传递,不需要写出(也不能写)。

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

class Teacher(Person):

    def __init__(self, name, gender, course):
        super(Teacher, self).__init__(name, gender)
        self.course = course

t = Teacher('Alice', 'Female', 'English')
print t.name
print t.course

3. 判断类型

函数isinstance()可以判断一个变量的类型,既可以用在Python内置的数据类型如str、list、dict,也可以用在我们自定义的类,它们本质上都是数据类型。

假设有如下的 Person、Student 和 Teacher 的定义及继承关系如下:

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

class Teacher(Person):
    def __init__(self, name, gender, course):
        super(Teacher, self).__init__(name, gender)
        self.course = course

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')

当我们拿到变量 p、s、t 时,可以使用 isinstance 判断类型:

>>> isinstance(p, Person)
True    # p是Person类型
>>> isinstance(p, Student)
False   # p不是Student类型
>>> isinstance(p, Teacher)
False   # p不是Teacher类型

这说明在继承链上,一个父类的实例不能是子类类型,因为子类比父类多了一些属性和方法。

我们再考察 s :

>>> isinstance(s, Person)
True    # s是Person类型
>>> isinstance(s, Student)
True    # s是Student类型
>>> isinstance(s, object)
True    # s是object类型
>>> isinstance(s, Teacher)
False   # s不是Teacher类型

s 是Student类型,不是Teacher类型,这很容易理解。但是,s 也是Person类型,因为Student继承自Person,虽然它比Person多了一些属性和方法,但是,把 s 看成Person的实例也是可以的。

这说明在一条继承链上,一个实例可以看成它本身的类型,也可以看成它父类的类型,即子类类型可以向上转型看做父类类型

4. 多态

类具有继承关系,并且子类类型可以向上转型看做父类类型,如果我们从 Person 派生出 StudentTeacher ,并都写了一个 whoAmI() 方法:

class Person(object):
    def __init__(self, name, gender):
        self.name = name
        self.gender = gender
    def whoAmI(self):
        return 'I am a Person, my name is %s' % self.name

class Student(Person):
    def __init__(self, name, gender, score):
        super(Student, self).__init__(name, gender)
        self.score = score
    def whoAmI(self):
        return 'I am a Student, my name is %s' % self.name

class Teacher(Person):
    def __init__(self, name, gender, course):
        super(Teacher, self).__init__(name, gender)
        self.course = course
    def whoAmI(self):
        return 'I am a Teacher, my name is %s' % self.name

在一个函数中,如果我们接收一个变量 x,则无论该  Person、Student还是 Teacher,都可以正确打印出结果:

def who_am_i(x):
    print x.whoAmI()

p = Person('Tim', 'Male')
s = Student('Bob', 'Male', 88)
t = Teacher('Alice', 'Female', 'English')

who_am_i(p)
who_am_i(s)
who_am_i(t)

运行结果:

I am a Person, my name is Tim
I am a Student, my name is Bob
I am a Teacher, my name is Alice

这种行为称为多态。也就是说,方法调用将作用在 x 的实际类型上。s 是Student类型,它实际上拥有自己的 whoAmI()方法以及从 Person继承的 whoAmI方法,但调用 s.whoAmI()总是先查找它自身的定义,如果没有定义,则顺着继承链向上查找,直到在某个父类中找到为止。

由于Python是动态语言,所以,传递给函数 who_am_i(x)的参数 x 不一定是 Person 或 Person 的子类型。任何数据类型的实例都可以,只要它有一个whoAmI()的方法即可:

class Book(object):
    def whoAmI(self):
        return 'I am a book'

这是动态语言和静态语言(例如Java)最大的差别之一。动态语言调用实例方法,不检查类型,只要方法存在,参数正确,就可以调用。

例子:

Python提供了open()函数来打开一个磁盘文件,并返回 File 对象。File对象有一个read()方法可以读取文件内容:

例如,从文件读取内容并解析为JSON结果:

import json
f = open('/path/to/file.json', 'r')
print json.load(f)

由于Python的动态特性,json.load()并不一定要从一个File对象读取内容。任何对象,只要有read()方法,就称为File-like Object,都可以传给json.load()

请尝试编写一个File-like Object,把一个字符串 r'["Tim", "Bob", "Alice"]'包装成 File-like Object 并由 json.load() 解析:

import json

class Students(object):
    def __init__(self, strlist):
        self.strlist=strlist
        
    def read(self):
        return self.strlist

s = Students('["Tim", "Bob", "Alice"]')

print json.load(s)

5. 多重继承

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

多重继承的继承链就不是一棵树了,它像这样:

class A(object):
    def __init__(self, a):
        print 'init A...'
        self.a = a

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

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

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

看下图:

                                                     

像这样,同时继承自 B 和 C,也就是 D 拥有了 A、B、C 的全部功能。多重继承通过 super()调用__init__()方法时,A 虽然被继承了两次,但__init__()只调用一次:

>>> d = D('d')
init A...
init C...
init B...
init D...

多重继承的目的是从两种继承树中分别选择并继承出子类,以便组合功能使用。

举个例子,Python的网络服务器有TCPServer、UDPServer、UnixStreamServer、UnixDatagramServer,而服务器运行模式有 多进程ForkingMixin 和 多线程ThreadingMixin两种。

要创建多进程模式的 TCPServer

class MyTCPServer(TCPServer, ForkingMixin)
    pass

要创建多线程模式的 UDPServer

class MyUDPServer(UDPServer, ThreadingMixin):
    pass

如果没有多重继承,要实现上述所有可能的组合需要 4x2=8 个子类。

例子:

+-Person
  +- Student
  +- Teacher

是一类继承树;

+- SkillMixin
   +- BasketballMixin
   +- FootballMixin

是一类继承树。

通过多重继承,请定义“会打篮球的学生”和“会踢足球的老师”:

class Person(object):
    pass

class Student(Person):
    def whoami(self):
        return 'student'

class Teacher(Person):
    def whoami(self):
        return 'teacher'

class SkillMixin(object):
    pass

class BasketballMixin(SkillMixin):
    def skill(self):
        return 'basketball'

class FootballMixin(SkillMixin):
    def skill(self):
        return 'football'

class BStudent(BasketballMixin, Student):
    pass

class FTeacher(FootballMixin, Teacher):
    pass

s = BStudent()
print s.whoami()
print s.skill()

t = FTeacher()
print t.whoami()
print t.skill()

 结果:

student
basketball
teacher
football
football

6. 获取对象信息:type()、 getattr()  setattr( )

拿到一个变量,除了用 isinstance() 判断它是否是某种类型的实例外,还有没有别的方法获取到更多的信息呢?

例如,已有定义:

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
    def whoAmI(self):
        return 'I am a Student, my name is %s' % self.name

首先可以用 type() 函数获取变量的类型,它返回一个 Type 对象:

>>> type(123)
<type 'int'>
>>> s = Student('Bob', 'Male', 88)
>>> type(s)
<class '__main__.Student'>

其次,可以用 dir() 函数获取变量的所有属性

>>> dir(123)   # 整数也有很多属性...
['__abs__', '__add__', '__and__', '__class__', '__cmp__', ...]

>>> dir(s)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__', '__getattribute__', '__hash__', '__init__', '__module__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'gender', 'name', 'score', 'whoAmI']

对于实例变量,dir()返回所有实例属性,包括`__class__`这类有特殊意义的属性。注意到方法`whoAmI`也是 的一个属性。

如何去掉`__xxx__`这类的特殊属性,只保留我们自己定义的属性?回顾一下filter()函数的用法。

dir()返回的属性是字符串列表,如果已知一个属性名称,要获取或者设置对象的属性,就需要用 getattr()  setattr( )函数了:

>>> getattr(s, 'name')          # 获取name属性
'Bob'

>>> setattr(s, 'name', 'Adam')  # 设置新的name属
>>> s.name
'Adam'

>>> getattr(s, 'age')           # 获取age属性,但是属性不存在,报错:
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute 'age'

>>> getattr(s, 'age', 20)      # 获取age属性,如果属性不存在,就返回默认值20:
20

例子:希望除了 name和gender 外,可以提供任意额外的关键字参数,并绑定到实例

class Person(object):

    def __init__(self, name, gender, **kw):
        self.name=name
        self.gender=gender
        # self.__dict__.update(kw)
        for k, v in kw.iteritems():
            setattr(self, k, v)

p = Person('Bob', 'Male', age=18, course='Python')
print p.age
print p.course

五、定制类

1. 特殊方法 / 魔术方法

1.1 特殊方法

  • 特殊方法定义在class中;
  • 不需要直接调用;
  • Python的某些函数或操作符会调用对应的特殊方法。

Python如何把任意变量变成str:__str__()

                            

如果给Person类加上__str__()这个特殊方法,就可以按自己的意愿打印实例输出:

1.2 特殊方法有哪些 

                    

1.3 如何正确实现特殊方法

  • 只需要编写用到的特殊方法;
  • 有关联性的特殊方法都必须实现:如实现了一个__getattr__,就必须同时实现__setattr__、__delattr__

2. python中 __str__和__repr__

如果要把一个类的实例变成 str,就需要实现特殊方法__str__():

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)

现在,在交互式命令行下用 print 试试:

>>> p = Person('Bob', 'male')
>>> print p
(Person: Bob, male)

但是,如果直接敲变量 p

>>> p
<main.Person object at 0x10c941890>

似乎__str__() 不会被调用。

因为 Python 定义了__str__()__repr__()两种方法,__str__()用于显示给用户,而__repr__()用于显示给开发人员。

有一个偷懒的定义__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__

3. python中 __cmp__

  • 对 intstr 内置数据类型排序时:Python的 sorted() 按照默认的比较函数 cmp 排序,即sorted()通过调用__cmp__()实现排序

  • 但是,如果对一组 Student 类的实例排序时:就必须提供我们自己的特殊方法 __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 self.name < s.name:
            return -1
        elif self.name > s.name:
            return 1
        else:
            return 0

上述 Student 类实现了__cmp__()方法,__cmp__用实例自身self和传入的实例 进行比较,如果 self 应该排在前面,就返回 -1,如果 s 应该排在前面,就返回1,如果两者相当,返回 0。

Student类实现了按name进行排序:

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

例子:请修改 Student 的 __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 self.score < s.score:
            return 1
        elif self.score > s.score:
            return -1
        else:
            if self.name < s.name:
                return -1
            elif self.name > s.name:
                return 1
            else:
                return 0

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

4. python中 __len__

如果一个类表现得像一个list,要获取有多少个元素,就得用 len() 函数。要让 len() 函数工作正常,类必须提供一个特殊方法__len__(),它返回元素的个数。

例如,我们写一个 Students 类,把名字传进去:

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

只要正确实现了__len__()方法,就可以用len()函数返回Students实例的“长度”:

>>> ss = Students('Bob', 'Alice', 'Tim')
>>> print len(ss)
3

例子:

斐波那契数列是由 0, 1, 1, 2, 3, 5, 8...构成。

请编写一个Fib类,Fib(10)表示数列的前10个元素,print Fib(10) 可以打印出数列的前 10 个元素,len(Fib(10))可以正确返回数列的个数10。

方式一:

class Fib(object):

    def __init__(self, num):
        self.num=num
        self.fib=[0,1]
        
        for i in range(1,9):
            if i<num:
                self.fib.append(self.fib[i-1]+self.fib[i])
                
        # 或者
        # i = 2
        # while i < self.num:
        #     self.fibo.append(self.fibo[i-2] + self.fibo[i-1])
        #     i = i + 1
    
    def __str__(self):
        return str(self.fib)

    def __len__(self):
        return self.num

f = Fib(10)
print f
print len(f)

方式二:

class Fib(object):
    def __init__(self, num):
        a, b, L = 0, 1, []
        for n in range(num):
            L.append(a)
            a, b = b, a + b
        self.numbers = L

    def __str__(self):
        return str(self.numbers)

    __repr__ = __str__

    def __len__(self):
        return len(self.numbers)

f = Fib(10)
print f
print len(f)

方式三:reduce()实现

class Fib(object):

    def __init__(self,num):
        fib=[0,1]
        for i in range(10):
            fib.append(reduce(lambda x,y: x+y,fib[i:i+2]))

        self.num=fib[:-2]

    def __str__(self):
        return str(self.num)

    def __len__(self):
        return len(self.num)
        

f = Fib(10)
print f
print len(f)

 

 

 

 

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Python进阶之路》是一本非常值得推荐的Python进阶书籍。这本书由一位经验丰富的Python大牛所著,作者拥有超过20年的Python开发经验。这本书涵盖了许多Python进阶知识点,如元编程、动态属性、属性描述符、异步处理等。书中详细列举了这些高级特性的使用方法,并讲解得非常透彻。如果你想从入门迈向进阶,这本书是必备的参考资料。 另外,《Python Cookbook》也是一本非常受欢迎的Python进阶书籍。这本书总结了大量精妙的编程技巧和实用的技术,无论你是Python新手还是老手,都会从中收获很多。豆瓣评分高达9.2分,可见其受到广大读者的认可。 除了以上两本书,《Python进阶技巧》也是一本非常值得一读的进阶书籍。这本书的作者将许多代码简化成了一行,展现了Python的高级技巧。虽然有些地方可能看起来有些夸张,但它确实帮助你了解Python的特性和一些不错的功能。而且,在关键时刻,这种技巧还可以让你轻松搞定其他人需要十几行代码才能完成的任务。对于想要进阶的同学来说,这本书的阅读是非常适合的。 总而言之,《Python进阶之路》、《Python Cookbook》和《Python进阶技巧》都是非常优秀的Python进阶书籍,适合想要深入学习Python的读者。 : 引用自《Python进阶之路》 : 引用自《Python Cookbook》 : 引用自《Python进阶技巧》

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

满腹的小不甘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值