【python进阶】python进阶技巧

'''2-1 在列表、字典、集合中根据条件筛选数据'''
'''总结 推荐使用解析 filter做知识扩展'''


from random import randint


l = [randint(-10, 10) for _ in range(10)]

# 第一种方法 列表解析 过滤小于0的数
res = [x for x in l if x >= 0]


# 第二种方法 filter返回生成器对象
list(filter(lambda x: x >= 0, l))


# 字典解析 随机生成20名学生的成绩
d = {
    
    'student%d' % i: randint(50, 100) for i in range(1, 21)}

# 第一种方法 筛选过90分的同学
res = {
    
    k: v for k, v in d.items() if v >= 90}


# 第二种方法 filter
dict(filter(lambda item: item[1] >= 90, d.items()))

# 集合 筛选能被3整除的数
s = {
    
    randint(0, 20) for _ in range(20)}
res = {
    
    x for x in s if x % 3 == 0}


'''2-2 为元组每个元素命名,提高程序可读性'''
'''总结 使用元组比字典更节省空间;内容较少时第一种不错,内容多的时候更喜欢第二种;总的来说枚举和namedtuple都是不错的选择'''


from enum import IntEnum
from collections import namedtuple


# 第一种方法 定义一系列数值常量或枚举类型
s = ('Jim', 16, 'male', '[email protected]')
NAME, AGE, SEX, EMAIL = range(4)


# 枚举
class StudentEnum(IntEnum):
    NAME = 0
    AGE = 1
    SEX = 2
    EMAIl = 3


print(s[StudentEnum.NAME])
# StudentEnum.NAME是整型的子类实例
print(isinstance(StudentEnum.NAME, int))


# 第二种方法 使用标准库collections.namedtuple替代内置tuple
Student = namedtuple('Student', ['name', 'age', 'sex', 'email'])
s2 = Student('Jim', 16, 'male', '[email protected]')

# s2仍是tuple的实例
print(isinstance(s2, tuple))
print(s2.name)


'''2-3 根据字典中值的大小对字典中的项进行排序'''
'''总结 推荐用sorted的key参数排序;掌握用解析和zip方法转化元组的做法'''


from random import randint


# 将字典中的各项转换为元组,使用内置函数sorted排序
d = {
    
    k: randint(60, 100) for k in 'abcdefgh'}

# 第一种转换方式
l = [(v, k) for k, v in d.items()]

# 第二种转换方式
l2 = list(zip(d.values(), d.keys()))

print(sorted(l, reverse=True))


# 使用sorted的key参数 直接对数值排序
p = sorted(d.items(), key=lambda item: item[1], reverse=True)

# enumerate提供序号 第二个参数为起始值
for i, (k, v) in enumerate(p, 1):
    # 更新字典 将排序结果放入字典
    d[k] = (i, v)

# 如果要把结果放入新字典 则可使用字典解析
d2 = {
    
    k: (i, v) for i, (k, v) in enumerate(p, 1)}


'''2-4 统计序列中元素的频度'''
'''总结 层层推进,最舒服的就是Counter,字典和堆的思想也很好'''


from random import randint
import heapq
from collections import Counter
import re


# 第一种方法 将序列转化为字典{元素: 频度},根据字典中的值排序
data = [randint(0, 20) for _ in range(30)]
d = dict.fromkeys(data, 0)
# 统计频度
for x in data:
    d[x] += 1

# 使用()即生成器解析更节省空间 取前几便用[:k]
print(sorted(((v, k) for k, v in d.items()), reverse=True))

# 在大量的数据中取前几个,上述方法就不理想,使用堆效果更好
print(heapq.nlargest(3, ((v, k) for k, v in d.items())))


# 第二种方法 使用标准库collections中的Counter对象
c = Counter(data)
c.most_common(3)


# 文本
txt = 'here have a big text'

# 使用非字母字符切割文本
word_list = re.split('\W+', txt)
c2 = Counter(word_list)
print(c2.most_common(2))


'''2-5 快速找到多个字典的公共键'''
'''总结 推荐第二种即reduce的方法'''


from random import randint, sample
from functools import reduce


sample('abcdefgh', randint(3, 6))
d1 = {
    
    k: randint(1, 4) for k in sample('abcdefgh', randint(3, 6))}
d2 = {
    
    k: randint(1, 4) for k in sample('abcdefgh', randint(3, 6))}
d3 = {
    
    k: randint(1, 4) for k in sample('abcdefgh', randint(3, 6))}


# 第一种方法
dl = [d1, d2, d3]
l = [k for k in dl[0] if all(map(lambda d: k in d, dl[1:]))]


# 第二种方法 利用集合set的交集操作
s1 = d1.keys()
s2 = d2.keys()
print(s1 & s2)

# reduce每次从后面取一个放到前面lambda中进行&运算
reduce(lambda a, b: a & b, map(dict.keys, dl))


'''2-6 让字典保持有序'''
'''总结 现在python版本的dict已经默认有序了,但掌握OrderedDict也是不错的选择;学会了随机打乱顺序的shuffle和具备迭代工具切片功能的islice,islice在普通的dict中也可使用'''


from collections import OrderedDict
from random import shuffle
from itertools import islice


players = list('abcdefgh')

# shuffle可以随机打乱顺序
shuffle(players)

od = OrderedDict()
for i, p in enumerate(players, 1):
    od[p] = i


# 按姓名查询
def query_by_name(d, name):
    return d[name]


# 按排名查询
def query_by_order(d, a, b=None):
    a -= 1
    if b is None:
        b = a + 1

    return list(islice(d, a, b))


print(od)
print(query_by_order(od, 4))
print(query_by_order(od, 3, 6))


'''2-7 实现用户的历史记录功能(最多n条)'''
''''总结 deque、pickle的使用'''


from collections import deque
import pickle


# 双端队列第一个参数为可迭代对象,第二个参数为队列大小,超出大小后自动弹出队首,记录新的内容
q = deque([], 5)
q.append(1)
q.append(2)
q.append(3)
q.append(4)
q.append(5)

# 以二进制方式写pkl文件,将要记录的内容存储到pkl里
pickle.dump(q, open('save.pkl', 'wb'))

# 读取存储的内容
q2 = pickle.load(open('save.pkl', 'rb'))


'''3-1 拆分含多种分隔符的字符串'''
'''总结 如果只有单个分隔符,推荐使用前两种方法;如果有多个分隔符,推荐使用第三种方法'''

from functools import reduce
import re

s = 'ab;cd|efg|hi,jkl|mn\topq;rst,uvw\txyz'


# 第一种方法
def my_split(s, seps):
    res = [s]
    for sep in seps:
        t = []
        list(map(lambda ss: t.extend(ss.split(sep)), res))
        res = t

    return res


print(my_split(s, ',;|\t'))


# 第二种方法
my_split2 = lambda s, seps: reduce(lambda l, sep: sum(map(lambda ss: ss.split(sep), l), []), seps, [s])

print(my_split2(s, ',;|\t'))


# 第三种方法
print(re.split('[,;|\t]', s))


'''判断字符串a是否以字符串b开头或结尾'''
'''总结 startswith和endswith方法比较实用;关于系统处理的一些方法也可了解一下'''


import os
import stat


fn = 'aaa.py'
print(fn.endswith('.py'))

# 多个时使用元组
print(fn.endswith(('.py', '.sh')))

# 读入目录下所有文件的文件名
d = os.listdir('.')

# 获取文件状态
s = os.stat('prac.py')

# 修改可执行权限
print(oct(s.st_mode))
oct(s.st_mode | 0o100)
print(oct(s.st_mode | 0o100))

# stat.S_IXUSR就是0o100
os.chmod('prac.py', s.st_mode | stat.S_IXUSR)

# 修改可执行权限的步骤
for fn in os.listdir('.'):
    if fn.endswith(('.py', '.sh')):
        fs = os.stat(fn)
        os.chmod(fn, fs.st_mode | stat.S_IXUSR)


'''3-3 调整字符串中文本的格式'''
'''总结 正则中顺序以及命名获取比较实用'''


import re

# 使用组顺序获取
print(re.sub('(\d{4})-(\d{2})-(\d{2})', r'\2/\3/\1', '2020-05-08'))

# 使用?P<name>为组命名 使用\g<name>获取
print(re.sub(r'(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})', r'\g<m>/\g<d>/\g<y>', '2020-05-08'))

# ord查询字符的ASCII码
ord('a')


'''3-4 将多个小字符串拼接成一个大字符串'''
'''总结 str.join()效率更高,推荐使用'''


from functools import reduce


l = ['sad', 'upset', 'happy']
s = ''

# 第一种方法 迭代列表,有很大缺点,造成时间和空间的浪费

# 使用reduce函数
print(reduce(str.__add__, l))


# 第二种方法 使用str.join()函数,表现更优秀
print(''.join(l))


'''3-5 对字符串进行左、右、居中对齐'''
'''总结 对齐方法值得掌握,两种都不错,想用哪个用哪个'''


s = 'abc'

# 第一种方法 *为填充字符可以不写
print(s.ljust(10, '*'))
print(s.rjust(10, '*'))
print(s.center(10, '*'))


# 第二种方法 *为填充字符可以不写,第一个参数不局限于字符串,数字也可以
print(format(s, '*<10'))
print(format(s, '*>10'))
print(format(s, '*^10'))

# 对于数字的额外知识 +表示总输出数字的正负,=表示符号不动,0表示填充
print(format(-123, '+'))
print(format(-123, '0=+10'))


d = {
    
    'lodDist': 100.0, 'SmallCull': 0.04, 'DistCull': 500.0, 'trilinear': 40, 'farclip': 477}

# 计算字典最长键的长度
w = max(map(len, d.keys()))

# 字典对齐输出
for k, v in d.items():
    print(k.ljust(w), ':', v)


'''3-6 去掉字符串中不需要的字符'''
'''总结 strip、切片、正则、replace、translate方法都可灵活运用'''


import re
import unicodedata


# 无参数去除
s = '  dasdad  '
print(s.strip())

# 带参数去除
s = '=+-==dsdasdasda==-+==='
print(s.strip('-+='))

# 切片去除
s = 'abc:1234'
print(s[:3]+s[4:])

# 替换为空串
s = '   asd  dasd   '
print(s.replace(' ', ''))

# 使用正则
print(re.sub('[ \t\n]+', '', s))
print(re.sub('\s+', '', s))

# translate 必须使用a的ASCII码
s = 'abc1234xyz'
print(s.translate({
    
    ord('a'): 'X', ord('b'): 'Y'}))
print(s.translate(s.maketrans('abcxyz', 'XYZABC')))

# 删除
print(s.translate({
    
    ord('a'): None}))

s = 'nǐ hǎo , chī fàn'

# 文本标准化 NFD表示字符应该分解为多个组合字符表示
s1 = unicodedata.normalize('NFD', s)

# 获取音调
l = [ord(c) for c in s1 if unicodedata.combining(c)]

# 注意这里是s1即文本标准化处理后的字符串
print(s1.translate(dict.fromkeys(l)))


'''4-1、2 实现可迭代对象和迭代器对象'''
'''总结 这一部分内容很有用,对迭代内部构造有了更深的理解,未来的项目开发中有可能会用到;学到了*的操作,很舒服'''


from abc import ABC
from collections.abc import Iterable, Iterator
import requests


# 迭代器对象是一次性消费的,对同一可迭代对象生成的两个迭代器对象间互不干扰
# 一个迭代器对象也是一个可迭代对象

# 实现用时访问,每个城市的气温都是在进行迭代的时候才进行网络访问

city = '北京'
url = 'http://wthrcdn.etouch.cn/weather_mini?city=' + city
r = requests.get(url)
print(r.text)
print(r.json())
print(r.json()['data']['city'])
print(r.json()['data']['forecast'][0])


# 天气迭代器对象
class WeatherIterator(Iterator, ABC):
    def __init__(self, cities):
        self.cities = cities
        self.index = 0

    def __next__(self):
        if self.index == len(self.cities):
            raise StopIteration

        city = self.cities[self.index]

        self.index += 1

        return self.get_weather(city)

    def get_weather(self, city):
        url = 'http://wthrcdn.etouch.cn/weather_mini?city=' + city
        r = requests.get(url)
        data = r.json()['data']['forecast'][0]

        # 默认便是返回一个元组
        return city, data['high'], data['low']


# 天气可迭代对象
class WeatherIterable(Iterable, ABC):
    def __init__(self, cities):
        self.cities = cities

    def __iter__(self):
        return WeatherIterator(self.cities)


# 用户展示
def show(w):
    for x in w:
        print(x)


# * 10模拟很多个城市,这个方法很实用
w = WeatherIterable(['北京', '上海', '广州'] * 10)

show(w)


'''4-3 使用生成器函数实现可迭代对象'''
from abc import ABC

'''总结 生成器对象可以自动维护迭代状态;理解了实现思路,但目前不知道可以用到哪些地方,但的确很厉害'''


from collections.abc import Iterable


class PrimeNumbers(Iterable, ABC):
    def __init__(self, a, b):
        self.a = a
        self.b = b

    def __iter__(self):
        for k in range(self.a, self.b + 1):
            if self.is_prime(k):
                yield k

    def is_prime(self, k):
        return False if k < 2 else all(map(lambda x: k % x, range(2, k)))


pn = PrimeNumbers(1, 30)
for n in pn:
    print(n)


'''4-3作业 用yield改造4-2code'''
from abc import ABC

from collections.abc import Iterable
import requests


# 天气可迭代对象
class WeatherIterable(Iterable, ABC):
    def __init__(self, cities):
        self.cities = cities

    def __iter__(self):
        for city in self.cities:
            yield self.get_weather(city)

    def get_weather(self, city):
        url = 'http://wthrcdn.etouch.cn/weather_mini?city=' + city
        r = requests.get(url)
        data = r.json()['data']['forecast'][0]

        return city, data['high'], data['low']


def show(w):
    for x in w:
        print(x)


w = WeatherIterable(['北京', '上海', '广州'] * 10)

show(w)


'''4-4 实现反向迭代'''
'''总结 实现__reversed__方法很厉害;关于python浮点数尾巴问题的处理'''


# 创建一个十进制描述的浮点数
from decimal import Decimal


class FloatRange:
    def __init__(self, a, b, step):
        self.a = Decimal(str(a))
        self.b = Decimal(str(b))
        self.step = Decimal(str(step))

    def __iter__(self):
        t = self.a
        while t <= self.b:
            yield float(t)
            t += self.step

    def __reversed__(self):
        t = self.b
        while t >= self.a:
            yield float(t)
            t -= self.step


fr = FloatRange(3.0, 4.0, 0.2)

for x in fr:
    print(x)

print('-' * 20)

for x in reversed(fr):
    print(x)


'''4-5 对迭代器做切片操作'''
'''总结 使用islice很方便'''


from itertools import islice


f = open('/Users/supremebeast/Desktop/python_impove.py')

# 下标从0开始,所以进行-1操作,不包括300即301行 还有一个可选参数步长
for line in islice(f, 100 - 1, 300):
    print(line)


# 一个简化的切片写法,当做理解
def my_slice(iterable, start, end, step=1):
    tmp = 0

    for i, x in enumerate(iterable):
        if i >= end:
            break

        if i >= start:
            if tmp == 0:
                tmp = step
                yield x
            tmp -= 1


print(list(my_slice(range(100, 300), 10, 20, 3)))
print(list(islice(range(100, 300), 10, 20, 3)))


'''4-6 在一个for语句中迭代多个可迭代对象'''
'''总结 数据比较简单时可以使用map,多时推荐使用zip;chain的使用非常新颖方便,掌握一种新的字符串切割方式'''


from random import randint
from itertools import chain
from functools import reduce


# 并行操作
chinese = [randint(60, 100) for _ in range(20)]
math = [randint(60, 100) for _ in range(20)]
english = [randint(60, 100) for _ in range(20)]

t = []

# 相同的求和操作
l = [sum(s) for s in zip(chinese, math, english)]
l2 = list(map(sum, zip(chinese, math, english)))
l3 = list(map(lambda s1, s2, s3: s1 + s2 + s3, chinese, math, english))


# 以下两条起到相同效果
list(map(lambda *args: args, chinese, math, english))
list(zip(chinese, math, english))


# 串行操作
c1 = [randint(60, 100) for _ in range(20)]
c2 = [randint(60, 100) for _ in range(20)]
c3 = [randint(60, 100) for _ in range(23)]
c4 = [randint(60, 100) for _ in range(25)]

# chain将多个列表数据串行方式放到一个列表中
ls = len([x for x in chain(c1, c2, c3, c4) if x > 90])


s = 'ab;cd|efg|hi,jkl|mn\topq;rst,uvw\txyz'

# 新的基于chain的字符串切割方法,*的作用是将chain里面的内容打散,不能是一个整体
print(list(reduce(lambda it_s, sep: chain(*map(lambda ss: ss.split(sep), it_s)), ',;|\t', [s])))

# 对*的进一步说明
print(list(chain([[1, 2], [3, 4]])))
print(list(chain(*[[1, 2], [3, 4]])))


'''读写文本文件'''
'''总结 读写文本文件的基本点'''


s = '读写文本文件'

# t为文本方式打开,内部操作编解码,默认为utf-8
f = open('b.txt', 'wt', encoding='gbk')
f.write(s)

# 冲入保存
f.flush()

# 读入时指定编解码格式,与写时保持一致
f = open('b.txt', encoding='gbk')
print(f.read())


'''6-1 读写csv数据'''
'''总结 掌握csv读写操作;delimiter指定分隔方式'''


import csv


wf = open('demo.csv', 'w')
writer = csv.writer(wf, delimiter=' ')
writer.writerow(['x', 'y', 'z'])
writer.writerow(['1', '2', '3'])
writer.writerow(['9', '8', '7'])

# 冲入到磁盘
wf.flush()


with open('demo.csv') as rf:
    # 读操作
    reader = csv.reader(rf, delimiter=' ')
    headers = next(reader)
    with open('demo_out.csv', 'w') as wf:
        # 写操作
        writer = csv.writer(wf, delimiter=' ')
        writer.writerow(headers)

        for d in reader:
            writer.writerow(d)


'''6-2 读写json数据'''
'''总结 json数据的读取;json与python对象的转换'''


import requests
import json


r = requests.get('http://httpbin.org/headers')

# 把json对象转换成python对象
d = json.loads(r.text)

# 把python对象转换成json对象
jd = json.dumps(d)

# 把json对象写入文件
with open('demo.json', 'w') as f:
    json.dump(jd, f)

# 加载json文件
with open('demo.json') as f:
    # 得到json对象
    data = json.load(f)
    # 转化为python对象
    d = json.loads(data)


'''6-5 读写Excel文件'''
'''总结 掌握基本的Excel读写操作'''


import xlrd
import xlwt


book = xlrd.open_workbook('demo.xlsx')
sheet = book.sheet_by_index(0)

# 得到type和value
c00 = sheet.cell(0, 0)

# 只要value
c11 = sheet.cell_value(1, 1)

# 获取第0行所有数据
sheet.row_values(0)

# 获取第1行从第1列开始的所有数据
sheet.row_values(1, 1)

# 写入操作,保存至新的表格
k = sheet.ncols

sheet.put_cell(0, k, xlrd.XL_CELL_TEXT, '总分', None)

for i in range(1, sheet.nrows):
    t = sum(sheet.row_values(i, 1))
    sheet.put_cell(i, k, xlrd.XL_CELL_NUMBER, t, None)

wbook = xlwt.Workbook()
wsheet = wbook.add_sheet(sheet.name)

for i in range(sheet.nrows):
    for j in range(sheet.ncols):
        wsheet.write(i, j, sheet.cell_value(i, j))

wbook.save('out.xls')


'''7-1 派生内置不可变类型并修改其实例化行为'''
'''总结 实现自定义规则元组,并将其实例化'''


# 自定义一种新的元组,继承内置tuple,在这个元组里拥有自定义的创建规则
class IntTuple(tuple):
    def __new__(cls, iterable):
        # 过滤iterable
        f_it = (e for e in iterable if isinstance(e, int) and e > 0)

        return super().__new__(tuple, f_it)


# 传入可迭代对象iterable
int_t = IntTuple([1, -1, 'abc', 6, ['x', 'y', 'z'], 3])


'''7-2 为创建大量实例节省内存'''
'''总结 如果创建大量实例且实例属性固定,则可使用slots方法关闭动态绑定;学到了__dict__的用处;set的减法使用'''


import sys


class Player1:
    def __init__(self, uid, name, level):
        self.uid = uid
        self.name = name
        self.level = level


class Player2:
    # 关闭动态添加属性,声明好共有哪些属性
    __slots__ = ['uid', 'name', 'level']

    def __init__(self, uid, name, level):
        self.uid = uid
        self.name = name
        self.level = level


p1 = Player1('0001', 'Jim', 20)
p2 = Player2('0001', 'Jim', 20)

# 多出的属性
print(set(dir(p1))-set(dir(p2)))

# 查看属性
print(p1.__dict__)

# 添加属性
p1.__dict__['z'] = 300

# 删除属性
p1.__dict__.pop('z')

# 查看使用的内存
print(sys.getsizeof(p1.__dict__))
print(sys.getsizeof(p1.uid))
print(sys.getsizeof(p1.name))
print(sys.getsizeof(p1.level))


# 只有p1可以动态添加属性,p2不可以,但相应的可以节省大量内存
p1.demo = 30


'''7-3 让对象支持上下文管理'''
'''总结 实现__enter__和__exit__方法,分别在with开始和结束被自动调用'''


class Player:
    def __init__(self, uid, name, level):
        self.uid = uid
        self.name = name
        self.level = level

    def __enter__(self):
        print("进入游戏")

        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print("退出游戏")


with Player('0001', 'Jim', 20) as player:
    print(player.__dict__)


'''7-4 创建可管理的对象属性'''
'''总结 实现在形式上是属性访问,实际上内部调用方法'''


class Player:
    def __init__(self, uid, name, level):
        self.uid = uid
        self.name = name
        self.level = level

    def get_player(self):
        return self.__dict__

    def set_player(self, info):
        if not isinstance(info["level"], int):
            raise TypeError("wrong level")
        if not isinstance(info["uid"], str) or not isinstance(info["name"], str):
            raise TypeError("wrong uid or name")

        self.uid = info["uid"]
        self.name = info["name"]
        self.level = info["level"]

    P = property(get_player, set_player)

    @property
    def lvl(self):
        return self.level

    @lvl.setter
    def lvl(self, level):
        self.level = level


# 第一种方法

player = Player('0001', 'Jim', 20)
player.P = {
    
    'uid': '0001', 'name': 'Jim', 'level': 30}

# 查看更新结果
print(player.P)


# 第二种方法

# @lvl.setter
player.lvl = 99

# @property
print(player.lvl)

# 查看更新结果
print(player.P)


'''7-5 让类支持比较操作'''
'''总结 运算符重载,使用total_ordering装饰器简化过程'''


from functools import total_ordering
from abc import ABCMeta, abstractmethod
import math


# @total_ordering只需有两个比较函数,即可推断其他比较函数
# 实现抽象方法area,留给子类继承,这样不同子类间也可进行比较
@total_ordering
class Shape(metaclass=ABCMeta):
    @abstractmethod
    def area(self):
        pass

    def __lt__(self, other):
        return self.area() < other.area()

    def __eq__(self, other):
        return self.area() == other.area()


class Rect(Shape):
    def __init__(self, w, h):
        self.w = w
        self.h = h

    def area(self):
        return self.w * self.h

    # __str__方法可以在打印时输出指定的数据
    def __str__(self):
        return 'Rect:(%s, %s)' % (self.w, self.h)


class Circle(Shape):
    def __init__(self, r):
        self.r = r

    def area(self):
        return self.r ** 2 * math.pi


rect1 = Rect(6, 9)
rect2 = Rect(7, 8)
c = Circle(8)

print(rect1)
print(rect1 <= rect2)
print(rect1 < c)
print(c > rect2)


'''7-6 使用描述符对实例属性做类型检查'''
'''总结 可对实例属性指定类型,赋予不正确类型抛出异常'''


class Attr:
    def __init__(self, key, type_):
        self.key = key
        self.type_ = type_

    def __set__(self, instance, value):
        if not isinstance(value, self.type_):
            raise TypeError('must be %s' % self.type_)

        instance.__dict__[self.key] = value

    def __get__(self, instance, owner):
        return instance.__dict__[self.key]

    def __delete__(self, instance):
        del instance.__dict__[self.key]


class Person:
    def __init__(self):
        self.name = Attr('name', str)
        self.age = Attr('age', int)

    def __str__(self):
        return "name: {}, age: {}".format(self.name, self.age)


p = Person()
p.name = 'Jim'
p.age = 20

print(p)


'''7-7 在环状数据结构中管理内存'''
'''总结 使用weakref.ref,创建一种能访问对象但不增加引用计数的对象'''


import weakref


class Node:
    def __init__(self, data):
        self.data = data
        self._left = None
        self.right = None

    def add_right(self, node):
        self.right = node

        # 左指针设置为弱引用
        node._left = weakref.ref(self)

    # 使得访问左结点与访问右结点格式一致,去掉()
    @property
    def left(self):
        return self._left()

    def __str__(self):
        return "Node:<%s>" % self.data

    def __del__(self):
        print("in __del__: delete %s" % self)


def create_linklist(n):
    head = current = Node(1)

    for i in range(2, n + 1):
        node = Node(i)
        current.add_right(node)
        current = node

    return head


# 创建双向链表
head = create_linklist(1000)

print(head.right, head.right.left)

# 释放内存
head = None


'''7-8 通过实例方法名字的字符串调用方法'''
'''总结 两种方法都可以,通过名字字符串调用方法,对于不同类间相同功能函数统一调用,可以参考这两种方法'''


from operator import methodcaller


class Triangle:
    def __init__(self, a, b, c):
        self.a, self.b, self.c = a, b, c

    def get_area(self):
        a, b, c = self.a, self.b, self.c
        p = (a + b + c) / 2
        return (p * (p - a) * (p - b) * (p - c)) ** 0.5


class Rectangle:
    def __init__(self, a, b):
        self.a, self.b = a, b

    def getArea(self):
        return self.a * self.b


class Circle:
    def __init__(self, r):
        self.r = r

    def area(self):
        return self.r ** 2 * 3.14159


def common_area(shape):
    method_name = ['area', 'get_area', 'getArea']

    for name in method_name:
        # 第一种方法
        f = getattr(shape, name, None)
        if f:
            return f()

        # 第二种方法
        if hasattr(shape, name):
            return methodcaller(name)(shape)


shape1 = Circle(1)
shape2 = Triangle(3, 4, 5)
shape3 = Rectangle(4, 6)

# 形状列表
shape_list = [shape1, shape2, shape3]

# 面积列表
area_list = list(map(common_area, shape_list))

print(area_list)


'''9-1 使用函数装饰器'''
'''总结 为多个函数统一添加某种功能,便可使用装饰器函数'''


# 定义装饰器函数,用它来生成一个在原函数基础上添加了新功能的函数,替代原函数
# 下面的例子便是加入数据缓存,func表示要被修饰的函数,*args接受任意多个参数,func与cache均构成闭包,所以缓存不会消失
def memo(func):
    # 缓存
    cache = {
    
    }

    def wrap(*args):
        # 去缓存中取数据
        res = cache.get(args)
        
        # 如果尚未存入缓存
        if not res:
            # 将参数放入被修饰函数,并记录在缓存中
            res = cache[args] = func(*args)

        return res

    return wrap


@memo
# 斐波那契问题
def fibonacci(n):
    if n <= 1:
        return 1

    return fibonacci(n - 1) + fibonacci(n - 2)


@memo
# 爬台阶问题
def climb(n, steps):
    count = 0
    if n == 0:
        count = 1
    elif n > 0:
        for step in steps:
            count += climb(n - step, steps)

    return count


# @memo等同于 xxx = memo(xxx),加入此装饰器后便无需写此赋值语句
print(fibonacci(50))
print(climb(100, (1, 2, 3)))


'''9-2 为被装饰的函数保存元数据'''
'''总结 使用wraps装饰器为被装饰的函数保存元数据;args接收位置参数,kwargs接收关键字参数'''


from functools import wraps


def my_decorator(func):
    @wraps(func)
    def wrap(*args, **kwargs):
        '''某功能包裹函数'''

        # 此处实现某种功能
        # ...

        return func(*args, **kwargs)

    return wrap


@my_decorator
def xxx_func(a, b):
    '''
    xxx_func函数文档:
    ...
    '''
    pass


print(xxx_func.__name__)
print(xxx_func.__doc__)


'''9-3 定义带参数的装饰器'''
'''总结 inspect.signature()提取函数签名;实现带参数的装饰器type_assert,用来修饰其他函数,约束被修饰函数的参数'''


import inspect


def type_assert(*ty_args, **ty_kwargs):
    def decorator(func):
        func_sig = inspect.signature(func)
        bind_type = func_sig.bind_partial(*ty_args, **ty_kwargs).arguments

        def wrap(*args, **kwargs):
            for name, obj in func_sig.bind(*args, **kwargs).arguments.items():
                type_ = bind_type.get(name)
                if type_:
                    if not isinstance(obj, type_):
                        raise TypeError("%s must be %s" % (name, type_))

            return func(*args, **kwargs)

        return wrap

    return decorator


@type_assert(int, list, str)
def f(a, b, c):
    pass

@type_assert(c=str)
def f2(a, b, c):
    pass


f(5, [], 'abc')
f2(5, 10, 'abc')


'''9-4 实现属性可修改的函数装饰器'''
'''总结 为包裹函数增添一个函数,用来修改闭包中使用的自由变量;使用nonlocal访问嵌套作用域中的变量引用'''


import time
import logging
import random


def warn_timeout(timeout):
    def decorator(func):
        def wrap(*args, **kwargs):
            # 开始计时
            t0 = time.time()
            
            # 运行func函数
            res = func(*args, **kwargs)
            
            # 计算用时
            used = time.time() - t0

            # 如若超过限定用时
            if used > timeout:
                # 打印日志消息
                logging.warning('%s: %s > %s', func.__name__, used, timeout)

            return res

        def set_timeout(new_timeout):
            # 用于声明闭包中的变量
            nonlocal timeout
            timeout = new_timeout

        wrap.set_timeout = set_timeout

        return wrap

    return decorator


@warn_timeout(1.5)
def f(i):
    print("in f [%s]" % i)
    while random.randint(0, 1):
        time.sleep(0.6)


for i in range(30):
    f(i)

# 更改自由变量
f.set_timeout(1)

for i in range(30):
    f(i)


'''9-5 在类中定义装饰器'''
'''总结 把类的实例方法作为装饰器,在包裹函数中可以持有实例对象,便于修改属性和拓展功能'''


# 此节代码为源码拷贝
import time
import logging

DEFAULT_FORMAT = '%(func_name)s -> %(call_time)s\t%(used_time)s\t%(call_n)s'


class CallInfo:
    def __init__(self, log_path, format_=DEFAULT_FORMAT, on_off=True):
        self.log = logging.getLogger(log_path)
        self.log.addHandler(logging.FileHandler(log_path))
        self.log.setLevel(logging.INFO)
        self.format = format_
        self.is_on = on_off

    # 装饰器方法
    def info(self, func):
        _call_n = 0

        def wrap(*args, **kwargs):
            func_name = func.__name__
            call_time = time.strftime('%x %X', time.localtime())
            t0 = time.time()
            res = func(*args, **kwargs)
            used_time = time.time() - t0
            nonlocal _call_n
            _call_n += 1
            call_n = _call_n
            if self.is_on:
                self.log.info(self.format % locals())
            return res

        return wrap

    def set_format(self, format_):
        self.format = format_

    def turn_on_off(self, on_off):
        self.is_on = on_off


# 测试代码
import random

ci1 = CallInfo('mylog1.log')
ci2 = CallInfo('mylog2.log')


@ci1.info
def f():
    sleep_time = random.randint(0, 6) * 0.1
    time.sleep(sleep_time)


@ci1.info
def g():
    sleep_time = random.randint(0, 8) * 0.1
    time.sleep(sleep_time)


@ci2.info
def h():
    sleep_time = random.randint(0, 7) * 0.1
    time.sleep(sleep_time)


for _ in range(30):
    random.choice([f, g, h])()

ci1.set_format('%(func_name)s -> %(call_time)s\t%(call_n)s')
for _ in range(30):
    random.choice([f, g])()

猜你喜欢

转载自blog.csdn.net/weixin_43359312/article/details/107320525