【Python系列 (一) 】Python基础疑难杂症

⭐️在这个怀疑的年代,我们依然需要信仰。

个人主页 :YYYing.

⭐️设计模式系列专栏:Python系列

系列下期内容:暂无


前言

python系列前几篇的基础疑难杂症主要是解决在学python时,个人觉得多少需要记忆或注意的点,也就是第一人称纯粹的笔记和学习视角来写的博客(个人向博客),基础好点的不用太仔细看,查缺补漏即可。

1. 函数

1.1 函数定义

想用到某个.py文件的函数可以用from 文件名(不包含.py) import 函数名

如果想用文件中所有的接口

from 文件名(不包含.py) import *

当然函数可以返回许多值,这些值其实是通过tuple返回的。

python 复制代码
def getposition(x,y):
    return x, y
python 复制代码
from func import *
num1 = 0; num2 = 0
num1, num2 = getposition(1,3)
print('num1 is %d, num2 is %d' %(num1,num2))
print('result is: ', getposition(1,3) )

1.2 参数

1.可变参数与关键字参数

可变参数使用和定义都非常方便

定义:

python 复制代码
def calsum(*nums):
    sum = 0
    for i in nums:
        sum = sum+ i
    return sum

使用:

python 复制代码
#-*-coding:utf-8-*-
from func import *
L=[1,3,5,7,9]
print(calsum(*L))
print(calsum(*range(0,101)))

定义可变参数前边加*,使用时可以传入list,tuple等,实参前也加*即可。

关键字参数其实和可变参数类似,关键字参数可以直接传入字典

定义:

python 复制代码
def getinfo2(name, age, **info):
    print("姓名:", name, "年龄:", age, "其他信息:", info)

def getinfo3(name,age,**info):
    if 'city' in info:
        print("有城市信息")
    if 'job' in info:
        print("有工作记录")
    print("姓名:", name, "年龄:", age, "其他信息:", info)

定义关键字参数在形参前边加**

使用:在实参前加**表示取出字典中所有内容依次传递给函数

python 复制代码
from func import *
info ={'性别':'女','job':'学生','city':'上海'}
info2 ={'性别':'男','job':'工人','city':'北京'}
getinfo2('韩梅梅', 23, **info)
getinfo3('李雷',25,**info2)

结果

2.命名关键字参数

命名关键字参数就是传入命名的关键字,如指定传入的关键字参数的key为'city','type'等等。

python 复制代码
def getinfo8(name, age, *,city, job):
    print("姓名:", name, "年龄:", age, "城市:", city, "工作:", job)
def getinfo7(name, age, *infolist, city, job):
    print("姓名:", name, "年龄:", age, "城市:", city, "工作:", job, "其他信息:", infolist)

可以看出命名关键字参数格式为(*,指定名称1,指定名称2...)的格式

当命名关键字参数之前的参数为可变参数时,那么不需要*,只需指明指定的几个名成即可

格式为:(*可变参数,指定名称1,指定名称2...)

python 复制代码
from func import *
info ={'性别':'女','job':'学生','city':'上海'}
info2 ={'性别':'男','job':'工人','city':'北京'}
getinfo2('韩梅梅', 23, **info)
getinfo3('李雷',25,**info2)
getinfo8('王麻子',70, city ='南京', job = '裁缝')
infolist = ['有犯罪记录','酗酒']
getinfo7('张三',50,*infolist,city ='翰林',job ='无业游民')

结果:

3.参数混合使用

当参数列表包含位置参数,默认参数,可变参数,关键字参数和命名关键字参数时,

从左到右的顺序为

位置参数,默认参数,可变参数,命名关键字参数,关键字参数

读者可以自己考虑下为什么这么规定

对应关系:

顺序 参数类型 传递方式 作用
1 普通位置参数 a, b 位置或关键字 最具体、最先匹配
2 默认参数 b=1 位置或关键字 可省略,但必须跟在无默认参数后
3 可变位置参数 *args 只收位置 兜底多余位置参数
4 命名关键字参数 c, d=2 只能关键字 强制用名字传,避免误传
5 可变关键字参数 **kwargs 只收关键字 兜底多余关键字参数

核心原因可以概括为:从确定到灵活,从位置到关键字,从具体到兜底。

例子我举一个,剩下感兴趣的可以下去再搜

调用时,Python 先按位置从左到右匹配参数。如果默认参数放在非默认参数前面:

python 复制代码
def f(a=1, b):  # SyntaxError
    ...

调用 f(2) 时,2 到底给 a 还是 b?无法确定。所以语法强制:非默认参数必须在默认参数之前。

下面定义两个函数:

python 复制代码
def getinfo5(name,age,city='沈阳',**info):
    print("姓名:", name, "年龄:", age, "城市:", city, "其他信息:", info)
def getinfo6(name,age,city='沈阳',*infolist ,health = '良好', job,**otherinfo):
    print("姓名:", name, "年龄:", age, "城市:", city, '工作信息:',job,'\n',
        "身体状况", health, "个人备注",infolist,'\n',
        "其他信息:", otherinfo)

使用这一系列定义的函数

python 复制代码
info5 ={'性别':'男','job':'工人','兴趣':'修自行车'}
baselist=('Linkn',28,'上海','喜欢喝冰啤酒','爱打麻将')

getinfo6(*baselist,**info5)
getinfo5('Linkn',28,'上海',**info5);

结果:


2. 生成器和迭代器

2.1 切片

list、tuple常常截取某一段元素,截取某一段元素的操作很常用 ,所以python提供了切片功能。

python 复制代码
L=['a','b','c','d','e','f']
#取索引0,到索引3的元素,不包括索引3
print(L[0:3])
#开始索引为0可以省略
print(L[:3])
#下标1到3
print(L[1:3])
#取最后一个元素
print(L[-1])
#取倒数后两个元素
print(L[-2:])
#取前四个数,每两个取一个
print(L[:4:2])
#所有数,每两个取一个
print(L[::2])

2.2 迭代

除了list、tuple可以迭代外,python中的dict类型变量也可以迭代。

python 复制代码
dictor = {'name':'Jul','age':17,'femail':1}
#迭代key
for key in dictor:
    print(key)    
#迭代value
for value in dictor.values():
    print(value)
#迭代key,value
for k,v in dictor.items():
    print(k,v)

可以将list变为索引元素对的形式

python 复制代码
for x,y in [(1,2),(3,4),(5,6)]:
    print(x,y)
#变为索引元素对
for i,value in enumerate(['A','B','C']):
    print(i,value)


#同时可以判断一个对象是否可以迭代

for x,y in [(1,2),(3,4),(5,6)]:
    print(x,y)
#变为索引元素对
for i,value in enumerate(['A','B','C']):
    print(i,value)

2.3 列表生成式

list函数可以将一组对象组合为列表,\[\]操作也可以。\[\]操作的方式称作列表生成式

print(x for x in range(1,11)) print(list(range(1,11))) 在列表生成式中可以加入一些运算规则,使生成的列表具备运算规则。

python 复制代码
#变为索引元素对
for i,value in enumerate(['A','B','C']):
    print(i,value)
#平方
print([x*x for x in range(1,11)])
#偶数平方
print([x*x for x in range(1,11) if x%2 ])
#k:v形式的列表
strdic={'a':'a1','b':'b1','c':'c1'}
print([k+':'+v for k,v in strdic.items()])
#将列表中字符串换为小写
L = ['Hello', 'World', 18, 'Apple', None]
print([s.lower() for s in L if(isinstance(s,str)) ])

2.4 生成器

python提供生成器的功能,生成器根据函数或运算规则产生一系列数据,

通过对返回值g调用next(g)可以依次取出生成的数据。

python 复制代码
g = (x*2 for x in range(1,11))
print(g)
print(next(g))

可以一直调用next(g),直到产生StopIteration异常。

当然也可以通过函数构造生成器,将函数return的关键字换为yield即可。

python 复制代码
#斐波那契数列
def fib(max):
    a,b,n = 0,1,0
    while n < max:
        yield b
        a,b=b,a+b
        n = n+ 1
    return "exit"

通过下面方式next取出数列中的元素,第三次调用会抛出StopIteration异常。

python 复制代码
g=fib(2)
print(g)
print(next(g))
print(next(g))
#print(next(g))

上面代码中g为迭代器,通过对g不断调用next取出数列中元素。

可以通过检测异常的方式完成遍历,避免程序崩溃。

python 复制代码
g2 = fib(6)

while True:
    try:
        value = next(g2)
        print("value: ", value)
    except StopIteration as e:
        print("Generator return value is: ", e)
        break

可以用生成器实现杨辉三角,生成器函数为triangles()。

生成器函数triangles()实现如下:

python 复制代码
def triangles():
    yield [1]
    yield [1,1]
    lists = [1,1]
    while True:
        i = 1
        n = len(lists)
        newlists = [1]
        while i < n:
            newlists.append(lists[i-1] + lists[i])
            i = i+1
        newlists.append(1)
        lists = newlists
        yield newlists

2.5 迭代器

python提供生成器的功能,生成器根据函数或运算规则产生一系列数据,

通过对返回值g调用next(g)可以依次取出生成的数据。g就是迭代器。

有的对象可以迭代但是不是迭代器,只有可以被next调用的对象才是迭代器。

同样可以通过isinstance函数判断迭代器。

python 复制代码
from collections.abc import Iterable
from collections.abc import Iterator

b1 = isinstance([], Iterable)
b2 = isinstance([], Iterator)
print('[] is Iteralbe', b1)
print('[] is Iterator', b2)

b1 = isinstance({},Iterable)
b2 = isinstance({},Iterator)

print('[] is Iteralbe', b1)
print('[] is Iterator', b2)

b1 = isinstance((x*x for x in range(10)), Iterable)
b2 = isinstance((x*x for x in range(10)), Iterator)
print('x*x for x in range(10) isIterable', b1)
print('x*x for x in range(10) isIterator', b2)

#可以被next()函数调用并不断返回下一个值的对象称为迭代器:Iterator

b1 = isinstance(triangles(),Iterable)
b2 = isinstance(triangles(),Iterator)
print('triangles()', b1)
print('triangles()', b2)

3. 函数式编程

3.1 函数对象

函数同样可以作为对象复制给一个变量,如下:

python 复制代码
f = abs;
print(f(-10))
f = 'abs';
print(f)

def add(a,b,f):
    return f(a) + f(b)

print(add(-1,2,f))

map 函数,map函数接受一个函数变量,第二个参数为一个可迭代对象,最后返回一个迭代器,由于迭代器的惰性,需要用list()函数返回所有元素。

python 复制代码
def squart(n):
    return n* n;

print(map(squart,range(1,11) ) )
print(list(map(squart,range(1,11) ) ))

reduce函数, reduce函数接受两个参数,第一个参数同样是函数对象f,f必须接受两个参数,并且返回和参数同类型的数据。第二个参数为一个可迭代序列。

python 复制代码
def func(a, b):
    return a + b

print(reduce(func, range(1,11)))

#reduce和map函数不一样,reduce返回的是一个最终值

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

可以通过reduce和map函数搭配,将一个字符串转化为整数

python 复制代码
def str2int(str):
    def char2int(c):
        return {'0':0,'1':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9}
    def convertnum(a,b):
        return a*10 + b
    return reduce(convertnum, map(char2int, str))

print(str2int("123456789"))

filter 函数,filter函数同样有两个参数,第一个参数为函数对象,返回值为bool类型,第二个参数为可迭代序列,返回值为迭代器,

同样需要list()转化为序列。下面用filter和生成器实现一个素数生成器函数

python 复制代码
# 构造奇数序列的生成器
def odd_generater():
    n = 1
    while True:
        n = n+2
        yield n

def primer_generater():
    yield 2
    #返回生成器地址
    it = odd_generater()
    print("it is ", it)
    while(True):
        #依次取出生成器的元素
        n = next(it)
        print("it2 is ", it)
        yield n
        #去掉可以被自己整除的元素返回新的生成器
        it = filter(lambda x:x%n > 0, it)

打印测试:

python 复制代码
for i in primer_generater():
    if(i < 100):
        print (i)
    else:
        break

sorted 函数,第一个接受一个list,第二个为比较的规则,可以不写

python 复制代码
print(sorted(["Abert","cn","broom","Dog"]) )

print(sorted(["Abert","cn","broom","Dog"], key = str.lower))

print(sorted(["Abert","cn","broom","Dog"], key = str.lower, reverse = True))

3.2 函数封装和返回

python 复制代码
def lazy_sum(*arg):
    def sum():
        x = 0
        for i in arg:
            x = x +i
        return x
    return sum

f = lazy_sum(2,3,1,6,8)
print(f())

定义了一个lazy_sum函数,函数返回内部定义的sum函数。可以在函数A内部定义函数B,调用A返回函数B,从而达到函数B延时调用。

闭包:

在函数A内部定义函数B,函数B内使用了函数A定义的局部变量或参数,这种情况就是闭包。

使用闭包需要注意,在函数B中修改了函数A 定义的局部变量,那么需要使用nonlocal关键字。如果在函数B中修改了全局变量,那么需要使用global关键字。

python 复制代码
def lazy_sum(*arg):
    sums = 0
    def sum():
        for i in arg:
            nonlocal sums
            sums = sums + i
        return sums
    return sum

f1 = lazy_sum(1,3,5,7,9)
f2 = lazy_sum(1,3,5,7,9)
print(f1 == f2)
print(f1() )
print(f2() )

匿名函数:lambda, lambda后面跟函数的参数,然后用:隔开,写运算规则作为返回值

python 复制代码
it = map(lambda x:x*x, (1,3,5,7,9))
print(list(it))

def lazy_squart():
    return lambda x:x*x
f = lazy_squart()
print(f(3) )

装饰器: 装饰器实际就是函数A中定义了函数B,并且返回函数B,为了实现特殊功能,如写日志,计算时间等等。

先看个返回函数,并且调用的例子

python 复制代码
def decoratorfunc(func):
    def wrapperfunc():
        print('func name is: %s'%(func.__name__))
        func()
    return wrapperfunc


def helloworld():
    print('Helloworld !!!')

helloworld = decoratorfunc(helloworld)
helloworld()

以后每次调用helloword,不仅会打印Helloworld,还会打印函数名字。

python提供装饰器的功能,可以简化上面代码,并且实现每次调用helloworld函数都会打印函数名字。

python 复制代码
def decoratorfunc(func):
    def wrapperfunc(*args, **kw):
        time1 = time.time()
        func(*args, **kw)
        time2 = time.time()
        print('cost %d secondes'%(time2-time1))
    return wrapperfunc

@decoratorfunc
def output(str):
    print(str)
    time.sleep(2)

output('hello world!!!')

如果函数带参数,实现装饰器可以内部定义万能参数的函数

装饰器执行@decoratorfunc相当于

output = decoratorfunc(output) output('hello world!!!')

如果装饰器需要传入参数,那么可以增加多一层的函数定义,完成装饰器参数传入和调用。

python 复制代码
def decoratorfunc(param):
    def decoratorfunc(func):
        def wrapperfunc(*arg, **kw):
            print('%s %s' %(param, func.__name__))
            func(*arg, **kw)
        return wrapperfunc
    return decoratorfunc

@decoratorfunc('execute')
def output(str):
    print(str)

output('nice to meet u')
print(output.__name__)

实际执行过程

python 复制代码
decorator = decoratorfunc('execute')
output = decorator(now)

output('nice to meet u')

执行print(output.name)发现打印出的函数名字不是output而是wrapperfunc,这对以后的代码会有影响。

可以通过python提供的装饰器@functools.wraps(func) 完成函数名称的绑定

python 复制代码
def decoratorfunc(param):
    def decoratorfunc(func):
        @functools.wraps(func)
        def wrapperfunc(*arg, **kw):
            print('%s %s' %(param, func.__name__))
            func(*arg, **kw)
        return wrapperfunc
    return decoratorfunc

@decoratorfunc('execute')
def output(str):
    print(str)

print(output.__name__)

print(output.name)显示为output,这符合我们需要的逻辑。

3.3 偏函数

如函数 int(a, base = 2) 可以实现一个字符串根据base提供的进制,转化成对应进制的数字。

可以通过偏函数,实现指定参数的固定,并且生成新的函数

python 复制代码
intnew = functools.partial(int, base = 2)
print(intnew('100'))

也可以自己定义函数:

python 复制代码
def add(a,b):
    return a+b
print(add(3,7))

addnew = functools.partial(add, 3)
print(addnew(7))

addnew2 = functools.partial(add, b = 7)
print(addnew2(3))

4. zip库的使用

4.1 zip的坑

zip()函数接收多个可迭代数列,将数列中的元素重新组合,在3.0中返回迭代器指向

数列首地址,在3.0以下版本返回List类型的列表数列。我用的是3.14版本python,

所以zip返回的是指向地址。

先看几个例子

可见,在3.0以上版本,对zip函数返回的结果采用list函数可以转化为列表。

通过列表生成式同样可以将zip结果化为列表。

当zip操作的对象为一个列表,那么生成的列表中每个元素(元祖)中为(n,)形式。当zip操作的多个列表长度不一样,那么zip返回生成的列表中元素个数为最短列表长度。

list函数可以将一个元祖转化为列表。下面可以将zip返回的数据转化为我们方便操作的列表元素.

这样将zip函数返回的数据通过list和迭代,生成了二维List,方便以后操作。下边这段代码在3.0版本以上和3.0版本以下会有不同结果。

python 复制代码
list1 = zip([1,3,5],[2,4,6])
print(list1)
print([row for row in list1])
print(list1)
print([list(row) for row in list1])

2.7版本结果

3.0版本结果

之前提起过zip在3.0以上版本返回迭代器指向内存地址。3.0以下版本返回的为列表,所以在3.0版本一下输出是符合最初目的。

但是3.0版本python最后一行输出却为空列表\[\]。这个原因主要是迭代器在被循环迭代或者访问后,会自动移动指针,指向下一个要迭代的元素。这和C++是不同的,

C++/C语言需要用户自己控制迭代器移位。那么肯定有人会说第一句和第三句打印的list1的值相同,是不是list1迭代器指向的空间没有移动呢?

不是的,只要list1被循环迭代,内部指向空间的地址就会变化,只是调用print打印list1时,python只返回迭代器指向空间的首地址,而不会告诉具体指向的地址空间。

修改下代码,看看是不是上文所述那样:

cpp 复制代码
list1 = zip([1,3,5],[2,4,6])
print(list1)
print([row for row in list1])
print(next(list1))
print([list(row) for row in list1])

结果:

可见,输出list1指向地址内容的时候出现StopIteration异常,这个异常前几篇介绍过,是因为迭代器已经指向空间的末尾了,再调用就会出现该异常,所以对于迭代器当遍历迭

迭代后一定要注意迭代器指向地址变化的问题。

4.2 矩阵的转置和左右逆置

通过zip函数可以实现矩阵的转置和逆置,

将矩阵按照每一行存储在一个list中,这些list再组合成一个大的list,构成二维list表示矩阵。

4.3 defaultdict函数介绍

实现一个统计字符串个数的功能。

python 复制代码
strings = ('puppy','kitten','popipa',''pipopa,
          'popipapa','pipopa')

如果用下边的代码实现统计功能

python 复制代码
strings = ('puppy','kitten','popipa',''pipopa,
          'popipapa','pipopa')
counts = {}

for kw in strings:
    counts[kw] += 1

当counts中不存在某个单词,第一次调用countskw+=1会提示报错。所以有几种方式实现该功能。

cpp 复制代码
strings = ('puppy','kitten','popipa',''pipopa,
          'popipapa','pipopa')
counts = {}

for kw in strings:
    if kw not in counts:
        counts[kw] = 1
    else:
        counts[kw] += 1

这种方式先判断counts中是否含有关键字,没有就先赋值。

cpp 复制代码
for kw in strings:
    if kw not in counts:
        counts[kw] = 1
    else:
        counts[kw] += 1

strings = ('puppy','kitten','popipa',''pipopa,
          'popipapa','pipopa')
counts = {}

for kw in strings:
    counts[kw] = counts.setdefault(kw, 0) + 1

这种方式通过设置counts中关键字对应的默认值,当counts中不存在某个关键字,

那么该关键字对应的value为0,然后该值+1,表示第一次统计。

如果counts中存在该关键字,那么就不执行setdefault函数,关键字对应的value值加1。

python 复制代码
from collections import defaultdict

strings = ('puppy','kitten','popipa',''pipopa,
          'popipapa','pipopa')

counts = defaultdict(lambda: 0)

这种方式引用了collections的defaultdict,difaultdict接收一个函数,该函数

返回dict中元素的默认值。

4.4 any函数介绍

any函数接收一个可迭代对象,一般为list或者tuple,list或者tuple中有一个

元素满足条件any函数就返回true,当所有元素都不满足条件,any返回false

python 复制代码
nums = [1,2,3,5,6]
print(any((i%2) for i in nums))
print(any((i > 12) for i in nums))
print(any([0, '', False]))
print(any(['a', 'b', '', 'd']))

5. 定制类和枚举

5.1 定制类

python定制类主要是实现特定功能,通过在类中定义特定的函数完成特定的功能。

python 复制代码
class Student(object):
    def __init__(self, name):
        self.name =name

student = Student("lilei")
print(student)

str用法

python 复制代码
class Student(object):
    def __init__(self, name):
        self.name = name
    def __str__(self):
        return ("self name is %s" %(self.name))

student2 = Student("hanmeimei")
print(student2)

实现__str__函数,可以在print类对象时打印指定信息

通过实现__iter__和__next__同样可以使类对象产生可迭代序列,下面实现了斐波那切数列。

python 复制代码
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 > 30:
            raise StopIteration()
        return self.a

__iter__返回一个可迭代对象,这里我们返回了Fib自己的对象。 __next__是根据迭代器返回每一个迭代的序列,这里实现了运算规则,返回对象的a成员。 打印输出

python 复制代码
for n in Fib():
    print(n)

getitem用法

可以实现__getitem__函数,这样就可以按照索引访问类对象中迭代元素了。

python 复制代码
class OddNum(object):
    def __init__(self):
        self.num = -1
    def __iter__(self):
        return self
    def __next__(self):
        self.num = self.num +2
        if self.num > 10:
            raise StopIteration()
        return self.num 

    def __getitem__(self,n):
        temp = 1
        for i in range(n):
            temp += 2
        return temp
python 复制代码
for n in OddNum():
    print(n)

oddnum = OddNum()
print(oddnum[3])

可以进一步完善OddNum类的__getitem__函数,使其支持切片处理

python 复制代码
def __getitem__(self, n):
    if isinstance(n ,int):
        temp =1
        for i in range(n):
            temp +=2
        return temp
    if isinstance(n, slice):
        start = n.start
        end = n.stop
        if start is None:
            start = 0
        tempList = []
        temp = 1
        for i in range(end):
            if i >= start:
                temp += 2
                tempList.append(temp)
        return tempList

可通过如下方式调用 print(oddnum[1:4])

getattr用法

通过实现__getattr__函数,可以在类对象中没有某个属性时,自动调用__getattr__函数 实现__call__函数,可以实现类对象的函数式调用

python 复制代码
def __getattr__(self,attr):
    if attr == 'name':
        return 'OddNum'
    if attr == 'data':
        return lambda:self.num
    raise AttributeError('\'OddNum\' object has no attribute \'%s\'' %attr)
def __call__(self):
    return "My name is OddNum!!"

只有在没有找到属性的情况下,才调用__getattr__,已有的属性不会在__getattr__中查找。

python 复制代码
print(oddnum.name)
print(oddnum.data)
#没有func函数会抛出异常
#print(oddnum.func)
#可以直接通过oddnum()函数式调用
print(oddnum())

5.2 定制类案例

下面是一个链式转化例子,用到了这些特定函数

python 复制代码
class Chain(object):
    def __init__(self, path=''):
        self.path = path
    def __getattr__(self,attr):
        return Chain('%s/%s'%(self.path, attr))
    def users(self, users):
        return Chain('%s/users/%s' %(self.path, users))
    def __str__(self):
        return self.path
    __repr__ = __str__
print(Chain().users('michael').repos)

repr 表示一个对象的字符串显示,当我们打印这个对象时,如果没有实现str 函数,则会调用repr 函数。 另外,当我们打印datetime对象时,可以看到repr 相比str 目标更具有准确性,str 目标在于可读性。 另外,在迭代器迭代打印的时候默认调用的是对象的repr 函数。 所以在我们实现了str 方法后,基本也会将其赋值给repr 函数。 具体的参考连接python中魔法方法__str__与__repr__的区别 - miaoning - 博客园 因为我们定义了getattr 函数,所以当属性不存在时就会构造一个新的对象返回,返回的对象拼接了之前的path信息和attr属性。 我们定义了users函数,这样可以调用users函数了。但是我们可以用更简洁的办法实现各种函数的调用而不用定义他们,就是通过实现call函数,这样就可以实现链式调用。

python 复制代码
class Chain(object):
    def __init__(self, path=''):
        self.path = path
    def __getattr__(self,attr):
        return Chain('%s/%s'%(self.path, attr))
    def __call__(self, param):
        return Chain('%s/%s'%(self.path, param))
    def __str__(self):
        return self.path
    __repr__ = __str__

print(Chain().get.users('michael').group('doctor').repos)

当调用users('michael')时,因为没有定义users函数,所以会调用getattr 函数,进而构造一个新的Chain对象。因为实现了call函数,所以这个对象可以被当作函数来使用,也就是Chain('michael')的方式。

5.3 enum枚举

python同样支持枚举操作

python 复制代码
from enum  import Enum
Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec') )
for name, member in Month.__members__.items():
    print(name, '=>', member, ',', member.value)

from enum import  unique
@unique
class Weekday(Enum):
    Sun = 0 # Sun的value被设定为0
    Mon = 1
    Tue = 2
    Wed = 3
    Thu = 4
    Fri = 5
    Sat = 6

for name , member in Weekday.__members__.items():
    print(name, '=>', member, ',', member.value)

6. 元类

python 可以通过type函数创建类,也可通过type判断数据类型

python 复制代码
import socket
from io import StringIO
import sys
class TypeClass(object):
    def typeprint(self, name = 'typeclass'):
        print('class name is %s' %(name))

typeclasses = TypeClass()
print(type(TypeClass))
print(type(typeclasses))

def printHW(self):
    print('Hello world!!!')
TypeClass2 = type('TypeClass2', (object,), dict(typeprinthw = printHW))
typeclass2 = TypeClass2()
typeclass2.typeprinthw()

type创建类格式为type('类名',(基类1,基类2...), dict(成员函数名=函数名)) 第一个参数为类名,第二个参数为一个tuple,如果继承的基类只有一个,要注意tuple写法(基类,),第三个参数为dict构造的类成员函数 除了可以用type创建类之外,可以用metaclass限制类的行为 使用metaclass需要先定义一个特定功能的元类,这个元类一般按照功能名字命名,末尾以Metaclass结束,表示一个特定功能的元类,这个元类必须继承于type类,内部必须实现 __new__借口完成类的限定。__new__的调用先于__init__,在对象构造之前调用。

python 复制代码
class DictMetaclass(type):
    def __new__(cls, name, bases, attrs):
        def insertfunc(self, key, value):
            self[key]=value
        attrs['insert'] = insertfunc
        return type.__new__(cls,name,bases,attrs)

class MyDict(dict, metaclass = DictMetaclass):
    pass

mydict = MyDict()
mydict.insert('nice', 3)
print(mydict['nice'])

定义了一个DictMetaclass元类,继承于type类,内部实现了new 方法,new 方法四个参数分别为准备创建的类对象,类名字,该类的基类集合,类的方法集合。并且new 内部调用了type类的new函数,完成了新功能属性的绑定。下面同样实现一个list类的扩展

python 复制代码
class ListMetaclass(type):
    def __new__(cls, name, bases, attrs):
        attrs['insert'] = lambda self, value: self.append(value)
        return type.__new__(cls,name,bases,attrs)

class Mylist(list,metaclass = ListMetaclass):
    pass

mylist = Mylist()
mylist.insert('name')
mylist.insert('age')
print(mylist)

下面为ORM框架例子 ORM全称"Object Relational Mapping",即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。 要编写一个ORM框架,所有的类都只能动态定义,因为只有使用者才能根据表的结构定义出对应的类来。 让我们来尝试编写一个ORM框架。 先实现Field类和其派生类

python 复制代码
class Field(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)

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

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

Field类用来管理数据库的字段和字段类型 实现ModelMetaclass这个元类,用于限制User类

python 复制代码
class ModelMetaclass(type):
    def __new__(cls,name,bases,attrs):
        if  name == 'Model':
            return type.__new__(cls, name, bases, attrs)
        print('Founc model: %s' % name)
        mappings = dict()
        for k, v in attrs.items():
            if isinstance(v,Field):
                print('Found mapping: %s ==> %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)

ModelMetaclass类中过滤了Model类的处理,将其他类的属性删除,将映射关系存储至mappings 属性字段,并且table字段用来存储类名。 下面实现Model类

python 复制代码
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"'Model' object 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))

Model 类继承了dict, 实现save函数,save函数将mappings属性值内容取出来,就是在ModelMetaclass中构造的mappings,其他类继承Model,调用save函数可以动态调用sql语句。 定义User类并且调用save

python 复制代码
class User(Model):
    # 定义类的属性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

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

输出如下:


结语

我是YYYing,后面还有更精彩的内容,希望各位能多多关注支持一下主包。

无限进步,我们下次再见!

相关推荐
rannn_1111 小时前
JVM 垃圾回收面试题全解析:从基础到进阶
java·开发语言·jvm·后端·面试
AINative软件工程1 小时前
LLM 应用的 Observability 三件套:Metrics、Logs、Traces 的生产级接入工程实践
后端·python·架构
知野小兔1 小时前
JavaScript 数组方法大全(超详细整理)
开发语言·前端·javascript
哈__1 小时前
飞牛OS还在手动维护?我用Ansible把重复操作写成了Playbook
开发语言·ansible·php
道尔柯南1 小时前
【QT】从零认识 Qt:跨平台 C++ GUI 开发入门
开发语言·c++·qt
二喵❥(^_-)1 小时前
接口测试流程的三个核心环节
开发语言·lua
喜欢coding的谢同学1 小时前
Go语言常用设计模式基础和实践案例介绍
开发语言·设计模式·golang
MayZork2 小时前
std::filesystem 文件操作详解
开发语言·c++·qt
JCHT1818182 小时前
源头厂家免拆维护:HT-6500H引领政企会议室革新
大数据·python