python高级特性的学习

本文详细介绍了Python的高级特性,包括切片、迭代、列表生成式、生成器、迭代器、函数式编程(高阶函数、装饰器、偏函数)、模块管理、面向对象编程(类和实例、数据封装、访问限制、继承和多态)以及高级编程技巧(使用slots、@property、多重继承、MixIn和定制类)。文章通过实例展示了如何利用这些特性提高代码的效率和可维护性。
摘要由CSDN通过智能技术生成

Python的学习

高级特性

切片

迭代

列表生产式

L=[]
for x in range(1,11):
    L.append(x*x)

但是循环太繁琐,而列表生成式则可以用一行语句代替生成上面的list:

[x*x for x in range(1,11)]

for循环后面还可以加上if判断,这样我们就可以筛选出仅偶数的平方:

[x*x for x in range(1,11) if x%2==0]

还可以使用两层循环,可以生成全排列

[m+n for m in ‘ABC’ for n in ‘XYZ’]
result:
[‘AX’,’AY’,’AZ’,’BX’,’BY’,’BZ’,’CX’,’CY’,’CZ’]

运用列表生成器,可以写出非常简洁的代码

import os
[d for d in os.listdir(‘.’)]
[‘.emacs.d’,‘.ssh’,...]

for循环其实可以同时使用两个甚至多个变量

for k,v in d.items():
    print(k,’=’,v)

裂变生成式也可以使用两个变量来生产list:

[k+’=‘+v for k,v in d.items()]

运用列表生产式,可以快速生产list,可以通过一个list推导出l另一个list,而代码十分简洁。

生成器

通过列表生成器,我们可以直接创建一个list。但是,受限于内存,列表容量肯定是有限的。然而,创建一个100万的元素的列表,不仅需要占用很大的空间。
所以,如果列表元素的可以按照某种算法推算出来,那我们是否可以在循环的过程中不断推算出后续的元素?这样我们就不需要大量的内存空间,从而节省了大量的内存空间。
在python中,一遍推导一遍计算的机制,称为生成器。generator

要创建一个generator,有很多种方法。第一种方法很简单,只要把列表生成器[]改成().就可以创建一个generator。

L=[x*x for x in range(10)]

g=(x*x for x in range(10))

创建L和g区别仅在于最外面层的【】和(),【】是一个列表,然而()则是一个generator。

可以通过next()函数来获得下一个返回值。

斐波拉契列用列

def fib(max):
    n,a,b=0,0,1
    while n<max:
        print b
        a,b=b,a+b
        n=n+1
    return ‘done’   

仔细观察,可以看出,fib函数实际上是定义了斐波拉契数列的推算规则,可以从第一个元素开始,推算出后续任意的元素,这种逻辑其实非常类似generator。

def fib(max):
    n,a,b=0,0,1
    while n<max:
        yield n
        a,b=b,a+b
        n=n+1
    return ‘done’       

这里,最难理解的就是generator和函数的执行流程不一样。函数是顺序执行,遇到return语句或者最后一行函数语句就返回。而变成generator的函数,在每次调用next()的时候执行,遇到yield语句返回,再次执行时从上次返回的yield语句处继续执行。

举一个简单的例子,定义一个gener,依次返回数字1,3,5:

def odd():
    print(‘step 1’)
    yield 1
    print(’step 2’)
    yield 3
    print(’step 3’)
    yield 5

调用该generator时,首先要生成一个generator对象,然后用next()函数不断获得下一个返回值:

o=odd()
next(o)
next(o)
next(o)

可以看到,odd不是普通函数,而是generator,在执行过程中,遇到yield就终端,下次有继续执行。执行3次yield后,已经没有yield可以执行了,所以,第4次调用next(o)就报错,

但是用for循环调用generator时,发现拿不到generator的return语句的返回值。如果想要拿到返回值,必须捕获stopIteration错误,返回值包含在StopIterationd的value中:

while True:
    try:
        x=next(g)
        print(‘g:’,x)
    exception StopInteration as e:
        break

迭代器

我们已经知道,可以直接作用域for循环的数据了下有以下几种:
一类是集合数据类:

  • 一类是集合数据类型,如list、tuple、dict、set、str等
  • 一类是generator,包括生成器和带yield的generator function
    这些可以直接作用于for循环的对象统称为可迭代对象:Iterable。
    可以使用isinstance()判断一个对象是否是Iterable对象
from collections import Iterable
isinstance([],Iterable)

而生产器不但可以作用于for循环,还可以被next()函数不断调用并返回下一个值,直到最后抛出StopIteration错误表示无法继续返回下一值了。
可以被next()函数调用并不断返回下一个值的对象陈给迭代器:Iterator.

isinstance(iter([]),Iterator)

函数式编程

高阶函数

map/reduce

python内建了map()和reduce()函数
我们先看map。map()函数接收两个参数,一个是函数,一个是iterable,map将闯入的函数依次作用到序列的每个元素,并把结果作为新的Iterator返回。
map()传入的第一个参数是f,即函数对象本身。由于结果r是一个Iterator是惰性序列。因此通过list()函数让它把整个序列都计算出来并返回一个list。

在看reduce的用法。reduce把一个函数作用一个序列[x1,x2,…],这个函数必须接收两个参数,reduce把结果继续和序列的下一个元素做累积计算。

filter

和map()类似,filter()也接收一个函数和一个序列。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是True和False决定保留还是丢失该元素。
filter的作用是从一个序列中筛选出符合条件的元素。由于filter()使用惰性计算,所以只有在取filter()结果的时候,

sorted

排序也是在程序中经常用到的算法。无论使用冒泡排序还是快速排序,排序的核心是比较两个元素的大小。如果是数字,我们可以直接比较,但是如果是字符串或者两个dict呢?直接比较数字上的大小还是没有意义的,因此,比较的过程必须通过函数抽象出来。

sorted([36,5,-12,9,-21])
sorted([36,5,-12,9,21],key=abs)

默认情况下,对字符串排序,是按照ASCII的大小比较,大写字母Z会排在小写字母a的前面。
现在,我们提出排序应该忽略大小写,按照字母序排序。要实现这个算法,不必对现有代码的大加改动,只要我们能用一个key函数把字符串映射为忽略大小写排序即可。忽略大小写比较两个字符串,实际上就是先把字符串都变成大写或者小写,再比较。

sorted([‘bob’,’about’,’Zoo’,’Credit’])
sorted([‘bob’,’about’,’Zoo’],key=str.lower)
sorted([‘bob’,‘about’,‘Zoo’],key=str.lower,reverse=True)
print(sorted(students,key=itemgetter(0)))
print(sorted(students,key=lambda t:t[1]))
print(sorted(students,key=itemgetter(1),reverse=True)

返回函数

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

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

装饰器

由于函数也是一个对象,而且函数对象可以被赋值给变量,所以,通过变量也能调用该函数。

def now():
    print (‘2005-3-25’)

f=now
f() 

函数对象有一个name属性,可以拿到函数的名字:

now.__name__
f.__name__

现在,假设我们要增强now()函数的功能,比如,在函数调用前后自动打印日志,但又不希望修改now()函数的定义,这种在代码运行期d动态增加的功能的方式,称之为装饰器。
本质上,decorator就是一个返回函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

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

观察上面的log,因为它是一个decorator,所以接受一个函数的高阶函数。所以,我们要定义一个能打印日志的decorator,可以定义如下:

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

调用now()函数,不仅会运行now()函数本身,还会运行now()函数前打印一行日志。
把@log放到now()函数的定义出,相当于执行了语句:

now=log(now)

由于log()是一个decorator,放回一个函数,所以,原来的now()函数仍然存在,只是现在同名的now变量执行新的函数,于是调用now()将执行新函数,即在log()函数中返回的wrapper()函数。
如果decorator本身需要传入参数,那就需要编写一个返回decorator的高阶函数,写出来更复杂。

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(‘execute’),放回的是decorator函数,在调用放回的函数,参数是now函数,返回值最终是wrapper函数。
以上两个钟decorator的定义都没有问题,但还差最后一步。因为我们讲了函数也是对象,它有name等属性,但你去看经过decorator装饰之后的函数,它们的name已经从原来的now变成了wrapper
因为返回的那个wrapper函数名字就是wrapper,所以,需要把yu的函数的name等属性复制到wrapper()函数中,否则,有些依赖函数签名的代码执行就会出错。

python内置的functools.wraps就是干这个事的,所以,一个完整的decorator的写法如下:

import functools

def log(func):
    @functools.wraps(func)
    def wrapper(*args,**kw):
        return func(*args,**kw)
    return wrapper   

偏函数

python的functools模块提供了很多有用的功能,其中一个就是偏函数。要注意,这里的偏函数和数学意义上的偏函数不一样。
在介绍函数参数的时候,我们讲到,通过设定参数的额默认值,可以降低函数。
int()函数还提供额外的base参数,默认值为10。如果传入base参数,就可以做N进制的转换。
int(‘1234’,base=8)
当传入:

max2=functools,partial(max,10)

实际上会把10作为*args的一部分自动加到左边,也就是

max2(5,6,7)

相当于

args=(10,5,6,7)
max(*args)

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

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

模块

使用模块

在一个模块中,我们可能会定义很多函数和变量,但有的函数和变量我们希望给别人使用,有的函数和变量我们希望仅仅在模块内部使用。在python中,是通过_前缀来实现的。

正常的函数和变量名是公开的,可以被直接引用。比如:abc,x123,PI等

类似xx这样的变量是特殊变量,可以被直接引用,但是有特殊用途,比如上面的authorname就是特殊变量,hello模块定义的文档注释也可以用来特殊变量doc访问,我们自己的变量一般不要用这种变量名。

类似_xxx和__xx这样的函数或变量就是非公开的(private),不应该被直接引用.比如_abc,__abc等

之所以我们说,

安装第三方模块

模块搜索路径

当我们试图加载一个模块时,python会在指定的路径下搜索对应的.py文件,如果找不到,就会报错:

默认情况下,python解释器会搜索当前目录、所有已安装的内置模块和第三方木模块,搜索路径存放在sys模块的path变量中

如果我们要添加自己的搜索路径,一是直接修改sys.path的路径。

面对对象

类和实例

面对对象最重要的是类和实例,必须牢记类时抽象的模板,比如student类,而实例是根据类创建出来的一个个具体的对象,每个对象都有着相同的方法,但各自的数据可能不同。

def student(object):
    pass

class后面紧接的是类名,紧接着是(object),表示该类是从哪个类继承下来的,继承的概念我们后面再讲,通常,如果没有合适的继承类,就使用object类,这是所有类最终都会继承的类。

>>> bart = Student()  
>>> bart  
<__main__.Student object at 0x10a67a590>  

后面的0x10a67a590是内存地址,每个object的地址都不一样,而student本身则是一个类。

可以自由的给一个实例变量绑定属性,比如,给bart绑定一个name的属性。

由于类可以起到模板的作用吗,因此,可以在创建实例的时候,把一些我们认为必须绑定的属性强制填写进去。通过定义一个特殊的init方法,在创建实例的时候,就把name,source等属性绑上去:

class student(object):
    def __init__(self,name,source):
        self.name=name
        self.source=source

注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向对象本身。

有了init方法的第一个参数永远是self,表示创建的实例本身,因此,在init的方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。

init方法,在创建实例的时候,就不能传入空的参数,比如传入与init方法匹配的参数,但self不需要传,python解释器自己会把实例变量传进去。

和普通的函数相比,在类中定义的函数只有一点不同,就是第一个参数永远是实例变量self,并且,调用时,不用传递该参数。除此之外,类的方法和普通的函数没有什么区别,所以,你仍然可以用默认参数、可变参数、关键字参数和命名关键字参数。

数据封装

面对对象编程的一个重要特点就是数据封装。在上面的student类中,每个实例就拥有各自的name和score这些数据。我们可以通过函数来访问这些数据,比如打印一个学生的成绩:

def print_score(std):
    print (%s :%s %(std.name,std.score))

但是,既然studen实例本身就拥有这些数据,要访问这些数据,就没有必要从外面的函数去访问,可以直接在student类的内部定义数据的函数,这样,就把数据封装起来了。这些封装数据的函数和student类本身是关联起来的,我们称之为类的方法:

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

    def print_score(self):
        print (%s:%s %s(self.name,self.score))  

要定义一个方法,除了第一个参数是self外,其它和普通函数一样。要调用一个方法,只需要在实例变量上直接调用,除了self,其它参数正常传入:

bart.print_score()

这样一来,我们从外部看student类,就只需要制定name和score,而如何打印,都是在student类的内部定义的,这些数据和逻辑被封装起来了,调用很容易,但却不用制定内部实现的细节。

封装的另一个好处是可以给student类增加新的方法。

访问限制

在class内部,可以有属性和方法,而外部代码可以通过直接调用实例变量的方法来操作数据,这样,就需要隐藏内部的复杂逻辑。

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

如果要让内部属性不被外部访问,可以把属性的名称前加上两个下划线,在python中,实例的变量名如果以开头,就变成了一个私有变量,只有内部可以访问,外部不能访问,所以,我们把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))

但是如果外部代码要获取name和score怎么办?可以给student类增加get_nameget_score这样的方法:

如果有要允许外部代码修改score怎么办?可以再给student类增加set_score方法:

class student(object):

    def set_score(self,score):
        self.__score=score

因为在方法中,可以对参数做检查,避免传入非法的参数

需要注意的是,在python中,变量名类似xxx,也就是双下划线开头,并以双下划线结尾的,是特殊变量,特殊变量是可以直接访问,不是private变量。

继承和多态

在oop程序设计中,当我们定义一个class的时候,可以从某个现有的class继承,新的class称为子类,而被继承的class称为基类、父类或超类。

对于dog来说,animal就是它的父类对于animal来说,dog就是它的子类,cat和dog类似。

继承有什么好处?最大的好处子类获得父类的全部功能。由于animal实现了run方法,因此,dog和cat作为它的子类,什么事也没有干,就自动拥有了run()方法。

继承的第二个好处需要我们对于代码进行一点点的改动,无论dog还是cat,它们的run的时候,都是现实animal is running,符合逻辑是的dog is running 和cat is running,因此,对于dog和cat分别进行了修改。

当子类和弗雷都存在相同的run()方法时,我们说,子类的run覆盖了父类的run(),在代码运行的时候,总是会调用子类的run().这样o我们就获得了继承的另一个好处:多态

要理解什么是多态,我们首先要对数据类型再作一点说明。我们顶一个class的时候,我们实际上就定义了一个数据类型。我们定义的数据类型和python自带的数据类型,比如str、list、dict没有什么两样。

判断一个变量是否某个类型可以用isintance()判断

dog可以看出animal,但animal不可以看出dog。

def run_twice(animal):
    animal.run()
    animal.run()

你会发现,新增一个animal的子类,不必对run_twice()做任何修改,实际上,任何依赖animal作为参数的函数或者方法都可以不加修改地正常运行,原因是因为多态。

这就是多态的真正的威力:调用方只管调用,不管细节,而当我们新增一个animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用。者就是著名的开闭原则:

对扩展开发:允许新增animal子类:

对修改封闭:不需要修改因爱animal类型的run_twice()等函数

静态语音vs动态语音
对于静态语言来说,如果需要传入animal类型,则传入的对象必须是animal类型或者它的子类,否则,将无法调用run()方法。

对于python这样的动态语言来说,则不一定需要传入animal类型,我们只需要保证传入的对象有一个run()方法就可以了。

获取对象信息

当我们拿到一个对象的引用时,如何知道这个对象是什么类型、有哪些方法呢?

使用type()

判断基本数据类型可以直接写int,str等,但如果要胖端一个对象是否是函数怎么办呢?可以使用types模块定义的常量:

import types

types.FunctionType
types.BuiltinFunctionType
types.LambdaType
types.GeneratorType
使用isinstance()

对于class的基础关系来说,使用type就很不方便。我们要判断class的类型,可以使用isinstance()函数。

isinstance(bar,Animal)
使用dir()

如果要获得一个对象的所有属性和方法,可以使用dir()函数,它返回一个包含字符串的list,比如,获得一个str对象的所有属性和方法。

dir(‘ABC’)
....

仅仅把属性和方法列出来是不够的,配合getattr(),setattr()以及hasattr(),我们可以直接操作一个对象的状态。

hasattr(obj,’x’)

obj.x

setattr(obj,’y’,19)

getattr(obj,’y’)

可以传入一个default参数,如果属性不存在,就放回默认值:

getattr(obj,’z’,404)

比较正规的写法如下:

def readImage(fp):
    if hasattr(fp,’read’):
        return readData(fp)
    return None 

实例属性和类属性

由于python是动态语言,根据类创建的实例可以任意绑定属性。
给实例绑定属性的方法是通过实例变量,或者通过self变量:

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

s=Student(‘Bob’)
s.score=90  

但是,如果student类本身需要绑定一个属性呢?可以至二级在class中定义属性,这种属性,归student类所有:

class student(object):
    name=’Student’
class Student(object):
    name=“Student”

s=Student()
print(s.name)   #因为实例并没有name属性,所以会继续查找class的name属性

从上面的例子可以看出,在编写程序的时候,千万不要对实例属性和类属性使用相同的名字,因为相同的实例属性将屏蔽掉类属性,但是当你删除实例属性后,再使用相同的名称,访问到的将是类属性。

面对对象高级编程

使用slots

正常情况下,当我们定义了一个class,创建了一个class的实例后,我们可以给该实例绑定任何属性和方法,者就是动态语言的灵活性。先定义class:

def Student():
    pass
s=Student()
s.name=‘Michael’
print (s.name)

还可以尝试给实例绑定一个方法:

def set_age(self,age):
    self.age=age

from types import MethodType
s.set_age=MethodType(set_age,s)
s.set_age(25)   

为了给所有实例都绑定方法,可以给class绑定方法

def set_score(self,score):
    self.score=score

Student.set_score=set_score 

通常情况下,上面的set_score方法可以直接在class中,但动态绑定允许我们在程序运行的中动态给class加上功能,这在静态语言中很难实现。

使用slots

但是,如果我们想要限制实例的属性怎么办?比如,只允许对Student实例添加name和age属性。

为了达到限制的目的,python允许在定义class的时候,定义一个特殊的slots变量,来限制class实例能添加的属性:

class Student(object):
    __slots__=(‘name’,’age’)#用tuple定义允许绑定的属性名称

除非在子类中也定义slots。这样,子类实例允许定义的属性就是自身的slots加上父类的slots.

使用@property

在绑定属性时,如果我们直接把属性暴露出去,虽然写起来很简单,但是,没办法检查参数,导致可以把成绩随便改:

这显然不合逻辑。为了限制score的范围,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成绩,这样,在set_score()方法里,就可以检查参数:

class Student(object):

    def get_score(self):
         return self._score

    def set_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

但是,上面的调用方法又略显复杂,没有直接用属性这么直接简单。

有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?对于追求完美的python程序员来说,

对于类的方法,装饰器一样起作用。python内置的@property负责把一个方法编程属性调用的:

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 a integer’)
        if value <0 or value >100:
            raise ValueError(‘score must between 0 ~ 100’)
        self._score=value       

@property的实现比较复杂,我们先考察如何使用。把一个getter方法变成属性,只需要加上@property就可以了,此时,@propery本身又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值,于是,我们就拥有了一个可控的属性操作。

注意到这个神奇的@property,我们在堆实例属性操作的时候,就知道该属性很可能不是直接暴露的,而是通过getter和setter方法实现的。

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

class Student(object):
    @property
    def birth(self):
        return self._birth

    @birth.setter
    def birth(self,value):
        self.birth=value

    @property
    def ages(self):
        return 2015-self._birth     

多重继承

继承是面向对象编程的一个重要的方式,因为通过继承,子类就可以扩展父类的功能。

如果要在增加‘宠物类’和‘非宠物类’,这么搞下去,类的数量会呈指数增长,很明显这样设计是不行的。

正确的做法是采用多重继承。首先,主要的层次扔按照哺乳类和鸟类设计:

class Animal(object):
    pass

class Mammal(Animal):
    pass

class Bird(Animal):
    pass

#各种动物
class Dog(Mammal):  
    pass

class Bat(Mammal):
    pass

class Parrot(Bird):
    pass

class Ostrich(Bird):
    pass                    

现在,我们要给动物在加上Runnable和Flyable的功能,只需要先定义好Runnable和Flyable的类

class Runnable(object):
    def run(self):
        print(‘Running...’)

class Flyable(object):
    def fly(self):
        print(‘Flying...’)      

对于需要Runnable功能的动物,就多继承一个Runnable,例如Dog:

class Dog(Mammal,Runnable):
    pass

对于需要Flybable功能的动物,就多继承一个Flyable,例如Bat:

class Bat(Mammal,Flyable):
    pass

通过多重继承,一个子类就可以同时获得多个父类的所有功能。

Mixln

在设计类的继承关系是,通常,主线都是单一继承下来的,例如,
Ostrich继承Bird。但是,如果需要混入额外的功能,通过多重继承就可以实现,比如,让Ostrich除了继承自Bird外,再同时继承runnable。这种设计通常称之为Mixin。

Mixin的目的就是给一个类增加多个功能,这样,在设计类的时候,我们优先考虑通过多重继承来组合多个Mixin的功能,而不是设计多层次的复杂的继承关系。

python自带的很多库也使用了Mixin。举个例子,python自带了TCPServer和UDPServer这两类网络服务,而要同时服务多个用户就必须使用多进程或多线程模型,这两种模型由forkingMixIn和ThreadingMixin提供。通过组合,我们就可以创造出合适的服务来。

比如,编写一个多进程的模式的tcp服务,定义如下:

class MyTCPSever(TCPServer,ForkingMixIn):
    pass

编写一个多线程模式的UDP服务,定义如下:

class MyUDPServer(UDPServer,ThreadingMixIn):
    pass

如果你打算搞一个更先进的协程模型,可以编写一个CoroutineMixIn:

class MyTCPServer(TCPServer,Corout)
    pass

这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。

定制类

看到类似slots这种形如xxx的变量或函数名就要注意,这些在python中是由特殊用途的。

slots我们已经知道怎么用了,除此之外,python的class中还有许多这样有特殊用途的函数,可以帮助我们定制类。

str

我们先定义个student类,打印一个实例:

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

怎么才能打印得好看呢?只需要定义好str方法,放回一个好看的字符串就可以了。

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

这样打印出来的实例,不但好看,而且容易看出实例内部重要的数据。
但是细心的朋友会发现直接敲变量不用print,打印出来的实例还是不好看。

这是因为直接显示变量调用的不是str,而是repr返回用户看到的字符串,而repr返回程序开发者看到的字符串,也就是说,repr是为了调试服务器。

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

如果一个类想被用于for… in循环,类似list或tuple那样,就必须实现一个iter方法,该方法返回一个迭代对象,然后,python的for循环就会不断调用该迭代对象的next()拿到循环的下一个值,直到stopIteration错误时推出循环。

class Fib(object):
    def __init__(self):
        self.a,self.b=0,1

    def __iter__(self):
        return self

    def __next__(self):
        self.a,self.b=self.b,self.a+self.b
        if self.a >10000:
            raise StopIteration()
        return self.a           
getitem

fib实例虽然能作用于for循环,看起来和list有点像,但是,把它当成list来使用还是不行,比如,取第5个元素:

Fib()[5]

class Fib(object):
    def __getitem__(self,n):
        a,b=1,1
        for x in range(n)
            a,b=b,b+a
        return a    
class Fib(object):
    def __getitem__(self, n):
        if isinstance(n, int): # n是索引
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
        if isinstance(n, slice): # n是切片 ** 也重要的
            start = n.start
            stop = n.stop
            if start is None:
                start = 0
            a, b = 1, 1
            L = []
            for x in range(stop):
                if x >= start:
                    L.append(a)
                a, b = b, a + b
            return L

也没有对负数作处理,所以,要正确实现一个getitem还是有很多工作要做的。

此外,如果把对象看出dict,getitem的参数也可能是一个可以作key的object,例如str。

与之对应的setitem方法,把对象视作list或dict来对集合赋值。最后还有一个delitem方法,用于删除某个元素。

总之,通过上面的方法,我们自己定义的类表现得和python自带的list、tuple、dict没有什么区别。

getattr

正常情况下,当我们调用类的方法或属性时,如果不存在,就会报错。比如定义。

class Student(object):
    def __init__(self):
        self.name=‘Michael’

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

当调用不存在的属性下,才调用getattr,已有的属性,比如name,不会再getattr中查找。此外,注意到任意调用如s.abc都会放回None,这是因为我们定义的getattr默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误:

call

一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。能不能直接在实例本省上调用呢?在python中,答案是肯定的。任何类,只需要定义一个call方法,就可以直接对实例进行调用。

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

    def __call__(self):
        print (‘My name is %s. ’% self.name)
s=Student(‘Michael’)
s()

call 还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看出函数,把函数看出对象,因此这两者之间本来及没啥根本的区别。

如果你把对象看出函数,那么函数本身其实也可以在运行期动态创建出来,因为类的实例都是运行期创建出来的,这么一来,我们就模糊了对象和函数的界限。

那么,怎么判断一个变量是对象还是函数呢?能被调用的对象就是一个Callable对象,比如函数和我们上面定义的带有call的类实例。

使用枚举类

当我们需要定义常量是,一个办法是用大写变量通过整数来定义,例如月份:

JAN=1
FEB=2
MAr=3
...
Nov=11
DEC=12

好处是简单,缺点是类型是int,并且仍然是变量。

更好的方法是为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。python提供了enum类来实现这个功能:

from enum import Enum

Month= Enum(‘Month’,(‘Jan’,‘Feb’,‘Mar’,‘Apr’,‘May’,‘Jun’,‘Jul’,‘Aug’,‘Sep’,‘Oct’,‘Nov’,‘Dec’))

这样我们就获得Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员:

for name,member in Month.__members__.items():
    print(name,’=>’,member,’,’,member.value)

value属性则是自动赋给成员的init常量,默认是从1开始计算。

如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:

from enum import Enum,unique

@unique
class Weekday(Enum):
    Sun = 0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

@unique 装饰器可以帮助我们检查包装没有重复值

day1=Weekday.Mon
print(day1)
Weekday.Mon
print(Weekday[‘Tue’])
Weekday.Tue
print(Weekday.Tue.value)
1
print(Weekday(1))
Weekday.Mon

可见,既可以用成员名称引用枚举常量,又可以直接根据value的值获得枚举常量。

使用元类

type()

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

比方说我们要定义一个Hello的class,就写一个hello.py模块:

class Hello(object):
    def hello(self,name=‘word’):
        print(‘Hello,%s’ % name )

当python解释器载入hello模块时,就会依次执行该模块的所有语句,执行结果就是动态创建出一个hello的class对象,测试如下

from hello import Hello
h=Hello()
h.hello()

type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型是type,而h是一个实例,它的类型就是class Hello。

我们说class的定义时运行时动态创建的,而创建class 的方法就是使用type()函数。

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

Hello=type(‘Hello’,(object,),dict(hello=fn)) #创建Hello class

要创建一个class对象,type()函数依次传入3个参数:

  • class的名称
  • 继承的弗雷集合,注意python支持多重继承,如果只有一个父类,别忘记tuple的单元素写法
  • class 的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上

通过type()函数创建的类和直接写class是完全一样的,因为python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。

metaclass

除了使用type()动态创建类之外,要控制类的创建行为,还可以使用metaclass

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

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

但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以,先定义metaclass,然后创建类。

连接起来就是:先定义metaclass,就可以创建类,最后创建实例。

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

metaclass是python面向对象最难理解,也是最难使用的魔术代码。正常情况下,你不会碰到需要使用metaclass的情况。

class ListMetaclass(type):
    def __new__(cls,name,bases,attrs):
        attrs[‘add’]=lambda self,value:self.append(value)
        return type.__new__(cls,name,bases,attrs)

有了ListMetaclass,我们在定义类的时候还要指示使用listMetaclass来定制类,闯入关键字metaclass:

class MyList(list,metaclass=ListMetaclass):
    pass

当我们闯入关键字参数metaclass时,魔术就生效了,它指示python解释器在创建myList时,要通过ListMetaclass.new()来创建,在此,我们可以修改类的定义,比如,加上新的方法,然后,返回修改后的定义。

new()方法接收到的参数依次是:
1 当前准备创建的类的对象
2 类的名字
3 类继承的父类集合
4 类的方法集合

但是,总会遇到需要通过metaclass修改类定义的。orm就是一个典型的例子。

ORM全称‘Object Relation Mapping’,即对象-关系映射,也就是一个类对应一个表,这样,写代码更简单,不用直接操作sql语句。

要编写一个orm框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。

让我们来尝试编写一个ORM框架。

编写底层模块的第一步,就是先把调用调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User类来操作对应的数据库表User,我们期待他写出这样的代码:

class User(object):

    id=IntegerField(‘id’)
    name=StringField(‘username’)
    email=StringField(‘email’)
    passowrd=StringField(‘password’)

u=User(id=12345,name=‘Michael’,email=‘test@orm.org’,password=‘my-pwd’)

u.save()

其中,父类Model和属性类型stringField、IntegerField是由ORM框架提供的,剩下的魔术方法比如save()全部由metaclass自动完成。虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。

现在,我们就按上面的接口来实现该ORM。

首先来定义Field类,它负责保存数据库表的字段名和字段类型。

class Filed(object):
    def __init__(self,name,column_type):
        self._name=name
        self._column_type=column_type

    def __str__(self):
        return ‘<%s:%s>’ %(self.__class__.__name__,self._name)  

在Field的基础上,进一步定义各种类型的Field,比如StringField,IntegerFieldd等等

class StringField(Field):
    def __init__(self,name):
        super(StringFiled,self).__init__(name,’varchar(100)’)

class IntegerField(Field):
    def __init__(self,name):
        super(IntegerField,self).__init__(name,’bigint’)        

下一步,就是编写最复杂的ModelMetaclass了:

class ModelMetaclass(type):
    def __new__(cls,name,bases,attrs):
        if name==‘Model’:
            return type.__new__(cls,name,bases,attrs)
        print(‘Found model:%s’% name)
        mappings=dict()
        for k,v in attrs.items():
            if isintance(v,Field):
                print(‘Found mapping:%s ==>’ %(k,v))                mappings[k]=v
        for k in mappings,keys():
            attrs.pop(k)
        attrs[‘__mappings__’]=mappings
        attrs[‘__table__’]=name
        return type.__new__(cls,name,bases,attrs)                       

以及基类Model:

class Model(dict,metaclass=ModelMetaclass):

    def __init__(self,**kw):
        super(Model,self).__init__(**kw)

    def __getattr__(self,key):
        try:
            return self[key]    
        except KeyError:
            raise AttributeError(r”’Modelobject has no attribute ‘%s’” % key)  

    def __setattr__(self,key,value):
        self[key]=value

    def save(self):
        fields=[]
        params=[]
        args=[]
        for k,v in self.__mappings__items():
            fields.append(v.name)
            params.append(‘?’)
            args.append(getattr(self,k,None))
        sql=‘insert into %s(%s) values (%s )’ % (self.__table__,’,’.join(fields),’,’.join(params))
        print(‘SQL:%s’ % sql)
        print(‘ARGS:%s’ % str(args))    

当用户定义一个class User(Model)时,Python解析器首先在当前类User的定义中查找metaclass,如果没有找到,就继续在父类Model中查找metaclass,找到了,就使用Model中定义的metaclass的ModelMetaclass来创建User类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到。

在ModelMetaclass中,一共做了几件事情:

  1. 排除掉队Model类的修改
  2. 在当前类中查找定义的类的所有属性,如果找到一个Field属性,就把它保存到一个mappings的dict中,同时从类属性中删除该Field属性,否则,容易造成运行时错误
  3. 把表名保存在table中,这里简化为表名默认为类名

在model类中,就可以定义各种操作数据库的方法。

我们事先了save() 方法,把一个实例保存到数据库中,因为有表名,属性到字段的映射和属性值的集合,就可以构造出insert语句。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值