Python知识点

Python:

常见面试题

  1. 直接赋值、浅拷贝和深度拷贝
  • 直接赋值:其实就是对象的引用(别名)。
  • 浅拷贝(copy):拷贝父对象,不会拷贝对象的内部的子对象。
  • 深拷贝(deepcopy): copy 模块的 deepcopy 方法,完全拷贝了父对象及其子对象。
    在python中,对象赋值实际上是对象的引用。当创建一个对象,然后把它赋给另一个变量的时候,python并没有拷贝这个对象,而只是拷贝了这个对象的引用
    
    copy浅拷贝,没有拷贝子对象,所以原始数据改变,子对象会改变
    
    深拷贝,包含对象里面的自对象的拷贝,所以原始对象的改变不会造成深拷贝里任何子元素的改变
    
  1. 推导式
  • print(sum([i for i in range(1, 5001) if i % 2 == 0]))
  • flattened = [i for row in matrix for i in row]
  • 一行代码就搞定,但一行里面有两个for,看起来很乱,两个for,哪个在前哪个在后呢?只要记住他们的顺序和不用推导式的原始for循环是一致的即可。
  • {value: key for key, value in input_dict.items()} # 字典
  • chars = {w[0] for w in words_list} # 集合
  1. tuple和list什么区别?
  • tuple:元组,list:列表
  • tuple的元素不可改(元组内的元素不可改,整个元组可修改)
  • list的元素可修改
  1. Python 垃圾回收机制
  • python 采用的是引用计数机制为主,标记 - 清除和分代收集两种机制为辅的策略。
    『引用计数法』的原理是:每个对象维护一个ob_ref字段,用来记录该对象当前被引用的次数,每当新的引用指向该对象时,它的引用计数ob_ref加1,每当该对象的引用失效时计数ob_ref减1,一旦对象的引用计数为0,该对象立即被回收,对象占用的内存空间将被释放.它的缺点是需要额外的空间维护引用计数,这个问题是其次的,最主要的是它不能解决对象的 “循环引用”
# 循环引用导致内存泄露
def f2():
    '''循环引用'''
    while True:
        c1=A()
        c2=A()
        c1.t=c2
        c2.t=c1
        del c1
        del c2
创建了c1,c2后,这两个对象的引用计数都是1,执行c1.t=c2和c2.t=c1后,引用计数变成2.
在del c1后,内存c1的对象的引用计数变为1,由于不是为0,所以c1的对象不会被销毁,同理,在del c2后也是一样的。
虽然它们两个的对象都是可以被销毁的,但是由于循环引用,导致垃圾回收器都不会回收它们,所以就会导致内存泄露。

  • 分代回收
    分代回收是一种以空间换时间的操作方式,Python 将内存根据对象的存活时间划分为不同的集合,每个集合称为一个代,Python 将内存分为了 3“代”,分别为年轻代(第 0 代)、中年代(第 1 代)、老年代(第 2 代),他们对应的是 3 个链表,它们的垃圾收集频率与对象的存活时间的增大而减小。
    新创建的对象都会分配在年轻代,年轻代链表的总数达到上限时,Python 垃圾收集机制就会被触发,把那些可以被回收的对象回收掉,而那些不会回收的对象就会被移到中年代去,依此类推,老年代中的对象是存活时间最久的对象,甚至是存活于整个系统的生命周期内。
    同时,分代回收是建立在标记清除技术基础之上。分代回收同样作为 Python 的辅助垃圾收集技术处理那些容器对象

  • 垃圾回收
    有三种情况会触发垃圾回收:

    • 调用gc.collect(),需要先导入gc模块。
    • 当gc模块的计数器达到阀值的时候。
    • 程序退出的时候。
  1. 字典,HASH

Python 的字典中有很多名字(“映射”、”哈希”、”散列”和”关系数组”),那你知道为什么字典会被称为Hash吗?你知道为什么字典对于键Key的存储要求很严格,但对于对应的值Value的存储却要求很宽松吗?

“散列函数是一种从任何一种数据中创建小的数字”指纹”的方法。散列函数把消息或数据压缩成摘要,使得数据量变小,将数据的格式固定下来。该函数将数据打乱混合,重新创建一个叫做散列值的指纹。散列值通常用来代表一个短的随机字母和数字组成的字符串。好的散列函数在输入域中很少出现散列冲突。在散列表和数据处理中,不抑制冲突来区别数据会使得数据库记录更难找到。”

Python 调用内部的散列函数,将键(Key)作为参数进行转换,得到一个唯一的地址(这也就解释了为什么给相同的键赋值会直接覆盖的原因,因为相同的键转换后的地址是一样滴),然后将值(Value)存放到该地址中。
对于 Python 来说,键(Key)必须是可哈希的,换句话说就是要可以通过散列函数计算出唯一地址的。那如果拿一个变量当键(Key)可以吗?肯定不行。因为变量随时都可能改变,不符合可哈希原则!

同样的,列表、字典、集合这些都是可变的,所以都不能做为键(Key)来使用。

那有朋友可能会问,那元祖呢?元祖总该是不变的吧?

其实不然,因为元祖里边可以存放列表这类可变因素,所以如果实在想拿元祖当字典的键(Key),那必须对元祖做限制:元组中只包括像数字和字符串这样的不可变元素时,才可以作为字典中有效的键(Key)。

另外还需要注意的一点是,Python 的哈希算法对相同的值计算得到的结果是一样的,也就是说 12315 和 12315.0 的值相同,他们被认为是相同的键(Key)。

数据结构和字符串

  1. map函数处理字典
a=dict(addr='china',name='samy')
dict(zip(a, map(lambda x: x.upper(), a.values())))  # 将两个迭代器 合并 成一个字典
[(index,data) for index,data in enumerate(a)]

console:
{'addr': 'CHINA', 'name': 'SAMY'}
[(0, 'addr'), (1, 'name')]
  1. 解压可迭代对象并赋值 利用*
    比如,你在学习一门课程,在学期末的时候,你想统计下家庭作业的平均成绩,但是排除掉第一个和最后一个分数。
first,*middle,last=grades # *params初始化时接收多个参数放到params里,此时params是个序列,再*params就是解包并返回多个参数
print(*middle)
  1. 保留最后N个元素 使用collections.deque
  2. 查找最大或最小的N个元素 使用heapq模块nlargest() 和 nsmallest() 堆排序算法
  3. 字典的运算 print(max(zip(prices.values(),prices.keys()))) # zip()函数创建的是一个只能访问一次的迭代器!
  4. 查找两字典的相同点 print(a.items() & b.items())
  5. 字符串操作 'somestring'.endswith(),startswith(),find()返回位置,replace()
  6. 正则:
import re
datepat = re.compile('r(\d+)/(\d+)/(\d+)')
r = re.match(datepat, text1)  # 返回个对象
text = 'Today is 11/27/2012. PyCon starts 3/13/2013.'
re.sub(r'(\d+)/(\d+)/(\d+)',r'\3-\1-\2',text)  # 复杂的替换
# 删除字符串中不需要的字符
s = ' hello world \n' 
print(s.strip()) # 删除开始或结尾的字符    lstrip() 和 rstrip() 分别从左和从右执行删除操作
re.sub('\s+', '', s) # 删除空格
  1. 列表生成表达式
a = [i for i in range(5) if i % 2 == 0]
print(a)
# 字典生成表达式
b = {i: i % 2 == 0 for i in range(10)}
print(b)
# 对于元组的生成表达式,其不叫元组生成表达式,叫生成器表达式
c = (c for c in range(10))
print(c)  # c 是一个generator
print(sum(c))  # 生成器表达式可以作为函数参数
print(sum(range(5))
迭代器和生成器
  1. 迭代器与生成器都可以使用for进行遍历
items=[1,2,3]
it=iter(items)  # items.__iter__()
next(it)  # it.__next__()
  1. 实现迭代器协议
def __iter__(self):
        return iter(self._children)
  1. 反向迭代 reversed() 函数 或者在对象里实现 reversed()
  2. 顺序迭代合并后的排序迭代对象 heapq.merge()
import heapq  # heapq.merge 可迭代特性意味着它不会立马读取所有序列。可以在非常长的序列中使用它,而不会有太大的开销
a = [1, 4, 7, 10]
b = [2, 5, 6, 11]
for c in heapq.merge(a, b):   # heapq.merge() 需要所有输入序列必须是排过序的。它并不会预先读取所有数据到堆栈中或者预先排序,也不会对输入做任何的排序检测。它仅仅是检查所有序列的开始部分并返回最小的那个,这个过程一直会持续直到所有输入序列中的元素都被遍历完。
    print(c)
  1. yield和send
# 这里我们要明确一点,yield的返回值和传给yield的值是两码事!!
def deco(func):
    def wrapper():
        res = func()
        a=next(res)  # 第一次要先执行next,或传None进去
        print(a)
        return res
    return wrapper
@deco
def foo():
    food_list = []
    print("start")
    while True:
        food = yield food_list  # 1. yield返回 food_list,为空 ,此时food还没有被赋值  2.第一次send时给yield传值,并赋值给了food,然后往下进行,再次停在yield时返回此时的food_list['苹果'] 3.第二次send,给yield传值,并赋值给了food,依次循环
        food_list.append(food)  
        print("elements in foodlist are:",food)

g = foo()
print(g.send('苹果'))
print(g.send('香蕉'))
print(g.send('菠萝'))

# send()方法具有两种功能:第一,传值,send()方法,将其携带的值传递给yield,注意,是传递给yield,而不是x,然后再将其赋值给x;第二,send()方法具有和next()方法一样的功能,也就是说,传值完毕后,会接着上次执行的结果继续执行,知道遇到yield停止
  1. 带额外状态信息的 回调函数
# 代码中需要依赖到回调函数的使用(比如事件处理器、等待后台任务完成后的回调等),并且你还需要让回调函数拥有额外的状态值,以便在它的内部使用到。  -- 异步处理
def apply_async(func, args, *, callback):
    # Compute the result
    result = func(*args)

    # Invoke the callback with the result
    callback(result)
def print_result(result):   # print_result() 函数仅仅只接受一个参数 result 。不能再传入其他信息。而当你想让回调函数访问其他变量或者特定环境的变量值的时候就会遇到麻烦
    print('Got:', result)
def add(x, y):
    return x + y
apply_async(add,(1,2),callback=print_result)
# 为了让回调函数访问外部信息,一种方法是使用一个绑定方法来代替一个简单函数。比如,下面这个类会保存一个内部序列号,每次接收到一个 result 的时候序列号加1
class ResultHandler:

    def __init__(self):
        self.sequence = 0

    def handler(self, result):
        self.sequence += 1
        print('[{}] Got: {}'.format(self.sequence, result))
r = ResultHandler()
apply_async(add, (2, 3), callback=r.handler)
# 第二种方式,作为类的替代,可以使用一个闭包捕获状态值
def make_handler():
    sequence = 0
    def handler(result):
        nonlocal sequence
        sequence += 1
        print('[{}] Got: {}'.format(sequence, result))
    return handler
handle=make_handler()
apply_async(add,(2,3),callback=handle)
# 还有另外一个更高级的方法,可以使用协程来完成同样的事情
def make_handler():
    sequence = 0
    while True:
        result = yield
        sequence += 1
        print('[{}] Got: {}'.format(sequence, result))
handler = make_handler()
next(handler)  # Advance to the yield
apply_async(add,(2,3),callback=handler.send) # 对于协程,你需要使用它的 send() 方法作为回调函数
apply_async(add,(1,3),callback=handler.send)
  1. 访问闭包内的变量
def sample():
    n = 0
    # Closure function
    def func():
        print('n=', n)

    # Accessor methods for n
    def get_n():
        return n

    def set_n(value):
        nonlocal n
        n = value

    # Attach as function attributes
    func.get_n = get_n
    func.set_n = set_n
    return func

s=sample()
s.set_n(10)
print(s.get_n())
s()
# 为了说明清楚它如何工作的,有两点需要解释一下。首先,nonlocal 声明可以让我们编写函数来修改内部变量的值。其次,函数属性允许我们用一种很简单的方式将访问方法绑定到闭包函数上,这个跟实例方法很像(尽管并没有定义任何类)。
# 还可以进一步的扩展,让闭包模拟类的实例。你要做的仅仅是复制上面的内部函数到一个字典实例中并返回它即可

类与对象

  1. __new__是在实例创建之前被调用的,因为它的任务就是创建实例然后返回该实例对象,是个静态方法。
    __init__是当实例对象创建完成后被调用的,然后设置对象属性的一些初始值,通常用在初始化一个类实例的时候。是一个实例方法。
    也就是: new先被调用,init后被调用,new的返回值(实例)将传递给init方法的第一个参数,然后init给这个实例设置一些参数。
  2. get getattr getattribute的区别
# __str__() 和 __repr__()
# __get__  __getattr__ __getattribute__ 的区别 
class A:

    def __init__(self, a):
        self.a = a

    # def __new__(cls, *args, **kwargs):
    #     print("__new__")
    #     return object.__new__(cls)
    
    # 如果class定义了__get__,则这个class就称为descriptor。owner是所有者的类,instance是访问 descriptor的实例,如果不是通过实例访问,而是通过类访问的话,instance则为None。
    # (descriptor的实例自己访问自己是不 会触发__get__,而会触发__call__,只有descriptor作为其它类的属性才有意义。)
    def __get__(self, instance, owner):
        print("visiting this item:", instance, owner)
        return self

    def __getattr__(self, item):  # 找不到属性时,最后会调用getattr,返回一个值或AttributeError异常
        print("no this item:", item)

    def __getattribute__(self, item):  # 无条件被调用,通过实例访问属性
        print("__getattribute__:", item)
        return object.__getattribute__(self, item)


a = A(2)
print(a.a)
a.b
class B:
    b=A(2)
print(B().b)  # b引用a,才会触发__get__

console:
__getattribute__: a
2
__getattribute__: b
no this item: b
***********
visiting this item: <__main__.B object at 0x0000019A4F68AE80> 
<__main__.A object at 0x0000019A4F68A4F0>
  1. 让对象支持上下文管理协议 探索自动化测试时启动一个数据库连接
# 你想让你的对象支持上下文管理协议(with语句)
# 为了让一个对象兼容 with 语句,你需要实现 __enter()__ 和 __exit__() 方法。例如,考虑如下的一个类,它能为我们创建一from socket import socket, AF_INET, SOCK_STREAM
from socket import AF_INET, SOCK_STREAM, socket


# 当出现 with 语句的时候,对象的 __enter__() 方法被触发,它返回的值(如果有的话)会被赋值给 as 声明的变量。然后,with 语句块里面的代码开始执行。最后,__exit__() 方法被触发进行清理工作。
# todo : 探索自动化测试时启动一个数据库连接?
class LazyConnection:
    def __init__(self, address, family=AF_INET, type=SOCK_STREAM):
        self.address = address
        self.family = family
        self.type = type
        self.sock = None

    def __enter__(self):
        if self.sock is not None:
            raise RuntimeError('Already connected')
        self.sock = socket(self.family, self.type)
        self.sock.connect(self.address)
        return self.sock

    def __exit__(self, exc_ty, exc_val, tb):
        self.sock.close()
        self.sock = None


from functools import partial

conn = LazyConnection(('www.python.org', 80))
# Connection closed
with conn as s:
    # conn.__enter__() executes: connection open
    s.send(b'GET /index.html HTTP/1.0\r\n')
    s.send(b'Host: www.python.org\r\n')
    s.send(b'\r\n')
    resp = b''.join(iter(partial(s.recv, 8192), b''))
    # conn.__exit__() executes: connection closed|
    
from pymysql import *


class DBConnect:
    def __init__(self, db_env):
        self._db_env = db_env

    def __enter__(self):
        if self._db_env == 'test':
            self.db = connect(
                host="127.0.0.1",
                port=3306,
                user='root',
                passwd='123456',
                db='test',
                charset='utf8'
            )
        else:
            pass

    def __exit__(self, exc_type, exc_val, exc_tb):
        pass

    def query(self):
        with db as f:
            cur = db.db.cursor()  # 默认游标类型为元组形式
            try:
                cur.execute('SELECT * FROM t_stu')
                result = cur.fetchall()  # 获取完数据后,数据会从数据集中删除,再次获取获取不到
                print(result)
            except Exception as e:
                print(e)


db = DBConnect('test')
db.query()
  1. 在类中封装属性名
  • Python程序员不去依赖语言特性去封装数据,而是通过遵循一定的属性和方法命名规约来达到这个效果。第一个约定是任何以单下划线_开头的名字都应该是内部实现
  • 大多数而言,你应该让你的非公共名称以单下划线开头。但是,如果你清楚你的代码会涉及到子类,并且有些内部属性应该在子类中隐藏起来,那么才考虑使用双下划线方案。
  • 双下划线开始会导致访问名称变成其他形式。这时候你可能会问这样重命名的目的是什么,答案就是继承——这种属性通过继承是无法被覆盖的 print(C()._C__private_method)
class B:
    def __init__(self):
        self.__private = 0

    def __private_method(self):
        pass

    def public_method(self):
        pass
        self.__private_method()

class C(B):
    def __init__(self):
        super().__init__()
        self.__private = 1 # Does not override B.__private

    # Does not override B.__private_method()
    def __private_method(self):
        pass
print(C()._C__private)
print(C()._C__private_method)
  1. 创建可管理的属性
# 你想给某个实例attribute增加除访问与修改之外的其他处理逻辑,比如类型检查或合法性验证。
class Person:

    def __init__(self, value):
        self.first_name = value  # 为什么 ``__init__() 方法中设置了 self.first_name 而不是 self._first_name 。在这个例子中,我们创建一个property的目的就是在设置attribute的时候进行检查。
                                 # 因此,你可能想在初始化的时候也进行这种类型检查。通过设置 self.first_name ,自动调用 setter 方法,这个方法里面会进行参数的检查,否则就是直接访问 self._first_name了
    
    @property    # 第一个方法是一个 getter 函数,它使得 first_name 成为一个属性。 其他两个方法给属性添加了 setter 和 deleter 函数
    def first_name(self):  # property的一个关键特征是它看上去跟普通的attribute没什么两样,但是访问它的时候会自动触发 getter 、setter 和 deleter 方法
        print("getting")
        return self._first_name   # _first_name``属性的操作,这也是实际数据保存的地方

    @first_name.setter
    def first_name(self, value):
        print("setting")
        self._first_name = value

    @first_name.deleter
    def first_name(self):
        print("deleting")
        # raise AttributeError("Can't delete attribute")

p = Person("wang")
p.first_name = 'www'
del p.first_name
  1. 调用父类方法
# Python是如何实现继承的。对于你定义的每一个类而已,Python会计算出一个所谓的方法解析顺序(MRO)列表。这个MRO列表就是一个简单的所有基类的线性顺序表。例如:
class Base:
    def __init__(self):
        print('Base.__init__')

class A(Base):
    def __init__(self):
        super().__init__()
        print('A.__init__')

class B(Base):
    def __init__(self):
        super().__init__()
        print('B.__init__')

class C(A,B):
    def __init__(self):
        super().__init__()  # Only one call to super() here
        print('C.__init__')
C.__mro__
# 实际上就是合并所有父类的MRO列表并遵循如下三条准则:

# 子类会先于父类被检查
# 多个父类会根据它们在列表中的顺序被检查
# 如果对下一个类存在两个合法的选择,选择第一个父类

console:
(__main__.C, __main__.A, __main__.B, __main__.Base, object)
  1. 创建新的类或实例属性 描述器类
# 一个描述器就是一个实现了三个核心的属性访问操作(get, set, delete)的类,分别为 __get__() 、__set__() 和 __delete__() 这三个特殊的方法。这些方法接受一个实例作为输入,之后相应的操作实例底层的字典
class Integer:
    def __init__(self, name):
        self.name = name

    def __get__(self, instance, cls):
        print("get instancs: ",instance)
        if instance is None:
            return self
        else:
            return instance.__dict__[self.name]

    def __set__(self, instance, value):
        print("set instancs: ",instance)
        if not isinstance(value, int):
            raise TypeError('Expected an int')
        instance.__dict__[self.name] = value

    def __delete__(self, instance):
        del instance.__dict__[self.name]
# 为了使用一个描述器,需将这个描述器的实例作为类属性放到一个类的定义中        
class Point:
    # 描述器的一个比较困惑的地方是它只能在类级别被定义,而不能为每个实例单独定义   self.x = Integer('x') 就不行
    x = Integer('x')
    y = Integer('y')

    def __init__(self, x, y):
        self.x = x
        self.y = y   
        
p = Point(2, 3)
p.x

# 描述其需要进一步理解,实际用到再回顾吧,总之一个类只要实现了__get__,__set__,__delete__中任意一个方法,我们就可以叫它描述器(descriptor)

console:
set instancs:  <__main__.Point object at 0x0000019A4CDC44F0>
set instancs:  <__main__.Point object at 0x0000019A4CDC44F0>
get instancs:  <__main__.Point object at 0x0000019A4CDC44F0>
2

8 .简化数据结构初始化

# 1 在一个基类中写一个公用的 __init__() 函数:
import math

class Structure1:
    _fields = []
    def __init__(self, *args):
        if len(args) != len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))
        # Set the arguments
        for name, value in zip(self._fields, args):
            setattr(self, name, value)
            
class Stock(Structure1):
    _fields = ['name', 'shares', 'price']

class Point(Structure1):
    _fields = ['x', 'y']
    
# 2 如果还想支持关键字参数,可以将关键字参数设置为实例属性
class Structure2:
    _fields = []

    def __init__(self, *args, **kwargs):
        if len(args) > len(self._fields):
            raise TypeError('Expected {} arguments'.format(len(self._fields)))

        for name, value in zip(self._fields, args):
            setattr(self, name, value) # 使用了 setattr() 函数类设置属性值  也可以利用字典self.__dict__.update(zip(self._fields,args))

        for name in self._fields[len(args):]:
            setattr(self, name, kwargs.pop(name))

        if kwargs:
            raise TypeError('Invalid argument(s): {}'.format(','.join(kwargs)))

class Stock(Structure2):
        _fields = ['name', 'shares', 'price']
s=Stock('ACME', 50, 91.1)
print(getattr(s, "name"))
print(s.__dict__)

console:
ACME
{'name': 'ACME', 'shares': 50, 'price': 91.1}
  1. 定义接口或者抽象基类 abc
# 定义一个接口或抽象类,并且通过执行类型检查来确保子类实现了某些特定的方法
from abc import ABCMeta, abstractmethod

class IStream(metaclass=ABCMeta):  # 抽象类的一个特点是它不能直接被实例化  a = IStream()这样是不行的
    @abstractmethod                # 抽象类的目的就是让别的类继承它并实现特定的抽象方法
    def read(self, maxbytes=-1): pass

    @abstractmethod
    def write(self, data): pass

# 抽象基类的一个主要用途是在代码中检查某些类是否为特定类型,实现了特定接口:
def serialize(obj, stream):
    if not isinstance(stream, IStream):
        raise TypeError('Expected an IStream')
    pass

# @abstractmethod 还能注解静态方法、类方法和 properties 你只需保证这个注解紧靠在函数定义前即可
class A(metaclass=ABCMeta):
    @property
    @abstractmethod
    def name(self):
        pass

    @name.setter
    @abstractmethod
    def name(self, value):
        pass

    @classmethod
    @abstractmethod
    def method1(cls):
        pass

    @staticmethod
    @abstractmethod
    def method2():
        pass

  1. 实现数据模型的类型约束
# 描述器、混入类、super() 的使用、类装饰器和元    https://www.kancloud.cn/kancloud/python3-cookbook/47266
# 混入类
def Typed(expected_type, cls=None):
    if cls is None:
        return lambda cls: Typed(expected_type, cls)
    super_set = cls.__set__

    def __set__(self, instance, value):
        if not isinstance(value, expected_type):
            raise TypeError('expected ' + str(expected_type))
        super_set(self, instance, value)
        print("Typed:",instance)

    cls.__set__ = __set__
    return cls


#  描述器
class Descriptor:
    def __init__(self, name=None, **opts):
        self.name = name
        for key, value in opts.items():
            setattr(self, key, value)

    def __set__(self, instance, value):
        print("Descriptor:",instance)
        instance.__dict__[self.name] = value

@Typed(int)
class Integer(Descriptor):
    pass


class Demo:
    x = Integer('x')

    def __init__(self,value):
        self.x=value
        print(self.x)

Demo(2)


console:
Descriptor: <__main__.Demo object at 0x0000019A4CD82430>
Typed: <__main__.Demo object at 0x0000019A4CD82430>
2
<__main__.Demo at 0x19a4cd82430>-
  1. 实现自定义容器
    collections 定义了很多抽象基类,可以自定义容器。比如你想让你的类支持迭代,那就让你的类继承 collections.Iterable 即可: 但是要实现 iter() 方法
    参考第四节
    class SortedItems(collections.Sequence):

  2. 属性的代理访问

# 将某个实例的属性访问代理到内部另一个实例中去(了解与继承的区别)
class A:
    def spam(self, x):
        return x

    def foo(self):
        pass

class B1:
    """简单的代理"""

    def __init__(self):
        self._a = A()

    def spam(self, x):
        return self._a.spam(x)

    def foo(self):
        return self._a.foo()

    def bar(self):
        pass
class B2:
    """使用__getattr__的代理,代理方法比较多时候"""

    def __init__(self):
        self._a = A()

    def bar(self):
        pass

    # Expose all of the methods defined on class A
    def __getattr__(self, name):
        """这个方法在访问的attribute不存在的时候被调用""" 
        return getattr(self._a, name)

b=B2()
b.spam(2) # 如果B2没有spam属性,则在对象的继承链上找,如果还找不到则调用__getattr__()
  1. 创建不调用init方法的实例
class Date:

    def __init__(self):
        self.year = "2020"

    @classmethod
    def today(cls):  # 在反序列对象或者实现某个类方法构造函数时需要绕过 __init__() 方法来创建对象
        d = cls.__new__(cls)
        t = localtime()
        d.year = t.tm_year
        return d

print(Date.today().year)
print(Date().year)

  1. 扩展类功能
# 1.暂时忽略:利用Mixins扩展类功能

# 2.一种实现混入类的方式就是使用类装饰
def LoggedMapping(cls):
    """第二种方式:使用类装饰器"""
    cls_getitem = cls.__getitem__
    cls_setitem = cls.__setitem__
    cls_delitem = cls.__delitem__

    def __getitem__(self, key):
        print('Getting ' + str(key))
        return cls_getitem(self, key)

    def __setitem__(self, key, value):
        print('Setting {} = {!r}'.format(key, value))
        return cls_setitem(self, key, value)

    def __delitem__(self, key):
        print('Deleting ' + str(key))
        return cls_delitem(self, key)

    cls.__getitem__ = __getitem__
    cls.__setitem__ = __setitem__
    cls.__delitem__ = __delitem__
    return cls


@LoggedMapping
class LoggedDict(dict):
    pass

d=LoggedDict()
d.__setitem__(1,2)
d.__getitem__(1)

console:
Setting 1 = 2
Getting 1
2
  1. 实现状态对象或者状态机
class ConnectionState:
    @staticmethod
    def read(conn):
        raise NotImplementedError()

    @staticmethod
    def write(conn, data):
        raise NotImplementedError()

    @staticmethod
    def open(conn):
        raise NotImplementedError()

    @staticmethod
    def close(conn):
        raise NotImplementedError()


class ClosedConnectionState(ConnectionState):
    @staticmethod
    def read(conn):
        raise RuntimeError('Not open')

    @staticmethod
    def write(conn, data):
        raise RuntimeError('Not open')

    @staticmethod
    def open(conn):
        conn.new_state(OpenConnectionState)

    @staticmethod
    def close(conn):
        raise RuntimeError('Already closed')


class OpenConnectionState(ConnectionState):
    @staticmethod
    def read(conn):
        print('reading')

    @staticmethod
    def write(conn, data):
        print('writing')

    @staticmethod
    def open(conn):
        raise RuntimeError('Already open')

    @staticmethod
    def close(conn):
        conn.new_state(ClosedConnectionState)


class Connection:
    """新方案——对每个状态定义一个类"""

    def __init__(self):
        self.new_state(ClosedConnectionState)

    def new_state(self, newstate):
        self._state = newstate
        # Delegate to the state class

    def read(self):
        return self._state.read(self)

    def write(self, data):
        return self._state.write(self, data)

    def open(self):
        return self._state.open(self)

    def close(self):
        return self._state.close(self)

c = Connection()
c.open()
c.read()

console:
reading
  1. 让类支持比较操作
from functools import total_ordering

class Room:
    def __init__(self, name, length, width):
        self.name = name
        self.length = length
        self.width = width
        self.square_feet = self.length * self.width
# 装饰器 functools.total_ordering 可以简化处理。 使用它来装饰一个类,你只需定义一个 __eq__() 方法,外加其他方法(lt, le, gt, or ge)中的一个即可。然后装饰器会自动为你填充其它比较方法。
@total_ordering
class House:
    def __init__(self, name, style):
        self.name = name
        self.style = style
        self.rooms = list()

    @property
    def living_space_footage(self):
        return sum(r.square_feet for r in self.rooms)

    def add_room(self, room):
        self.rooms.append(room)

    def __str__(self):
        return '{}: {} square foot {}'.format(self.name,
                self.living_space_footage,
                self.style)

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

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

h1 = House('h1', 'Cape')
h1.add_room(Room('Master Bedroom', 14, 21))
h1.add_room(Room('Living Room', 18, 20))
h1.add_room(Room('Kitchen', 12, 16))
h2 = House('h2', 'Ranch')
h2.add_room(Room('Master Bedroom', 14, 21))
print('Is h1 bigger than h2?', h1 > h2) # prints True
print('Is h2 smaller than h3?', h2 < h3) # prints True

console:
Is h1 bigger than h2? True
Is h2 smaller than h3? True

装饰器

def decorator(func):
    def wrapper(*args, **kwargs):
        print(1)
        func(*args, **kwargs)

    return wrapper

@decorator
def fun():
    print("222")

# func()等效于如下   func调用,就等于wrapper调用
d=decorator(fun) # func传入装饰器后,返回的是wrapper函数
d() # wrapper调用   func(*args) == wrapper(*args)

# 注意这种会打印两次print(1),但是直接运行func()就会正常 ,这样运行时去掉那个装饰器就行了

def decorator():
    def wrapper(func):
        def run(*args):
            print(args)
            func(*args)

        return run

    return wrapper


@decorator()  # 注意添加()
def func():
    print("222")


# func()等效于如下
d = decorator()  # 返回的是wrapper函数
wrapper = d(func)  # wrapper函数调用,传参func,等于wrapper(func),返回的run函数
wrapper()  # run()

console:
1
1
222
()
()
222
  1. 类装饰器
# 类 装饰器两种基本写法,  一个是init传func,一个是call里传func
class A:

    def __init__(self):
        pass

    def __call__(self, func):
        print("calling")
        return func


@A()  # 这种需要添加(),先实例化,然后调用类,传入add方法
def add(x, y):
    print(x + y)


# a=A()
# a()
add(1, 2)


class B:

    def __init__(self, func):
        self._func = func

    def __call__(self, *args):
        print("calling")
        self._func(*args)


@B  # 这种不需要添加(),实例化时传入func并设置变量,调用时调用func并传入参数
def add(x, y):
    print(x + y)


b = B(add)
b(2, 2)
# add(1,2)





#  类装饰器给类装饰
#  类装饰器给类装饰
#  类装饰器给类装饰

class B:

    def __init__(self, func):
        wraps(func)(self)  # 1
        # self._func = func

    def __call__(self, *args):
        print("calling")
        # self._func(*args)
        return self.__wrapped__(*args)  # 2

    def __get__(self, instance, cls): # 3
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

# @B
class C:

    def __init__(self):
        print("init")

    def __call__(self, *args, **kwargs):
        pass

    def add(self,x,y):
        print(x+y)

# c=C()
# c.add(2,3)
b=B(C)
c=b() # 返回的是C对象
c.add(2,3) 

console:
calling
3
calling
calling
4
calling
init
5
  1. 创建装饰器时保留函数元信息
# 你写了一个装饰器作用在某个函数上,但是这个函数的重要的元信息比如名字、文档字符串、注解和参数签名都丢失了。
# 任何时候你定义装饰器的时候,都应该使用 functools 库中的 @wraps 装饰器来注解底层包装函数
import time
from functools import wraps

def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end,start)
        return result
    return wrapper

@timethis
def countdown(n:int):
    """this is a demo"""
    print("running")
    while n > 0:
        n -= 1

countdown(100)      
print(countdown.__name__)
print(countdown.__doc__)
# @wraps 有一个重要特征是它能让你通过属性 __wrapped__ 直接访问被包装函数。例如:
countdown.__wrapped__(100000) # 输出running,但是不执行装饰器
# __wrapped__ 属性还能让被装饰函数正确暴露底层的参数签名信息。例如:
from inspect import signature
print(signature(countdown))
  1. 带参数的装饰器
@decorator(x, y, z)
def func(a, b):
    pass
# 等效于

def func(a, b):
    pass
func = decorator(x, y, z)(func)
#decorator(x, y, z) 的返回结果必须是一个可调用对象,它接受一个函数作为参数并包装它
  1. 可自定义属性的装饰器
from functools import wraps, partial
import logging


def attach_wrapper(obj, func=None):
    print(func)
    if func is None:
        return partial(attach_wrapper, obj)  # partial可以用来"冻结"一个函数的参数,并返回"冻结"参数后的新函数,这里相当于两次调用,符合带参数的装饰器,第一次obj为add,func为空,第二次,obj为add,func为logged.set_level
    setattr(obj, func.__name__, func)  # 给func添加属性,set_message=set_message() 和 set_level=set_level()
    return func


def logged(level, name=None, message=None):
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)

        # 访问函数(如 set_message() 和 set_level() ),它们被作为属性赋给包装器
        @attach_wrapper(wrapper)
        def set_level(newlevel):
            nonlocal level
            level = newlevel

        @attach_wrapper(wrapper)
        def set_message(newmsg):
            nonlocal logmsg
            logmsg = newmsg

        return wrapper

    return decorate


@logged(logging.DEBUG)
def add(x, y):
    return x + y


@logged(logging.CRITICAL, 'example')
def spam():
    print('Spam!')


logging.basicConfig(level=logging.DEBUG)
add(2, 3)
spam()
add.set_message('Add called')
add(1, 3)
add.set_level(logging.WARNING)
add(1, 3)

console:
DEBUG:__main__:add
CRITICAL:example:spam
DEBUG:__main__:Add called
WARNING:__main__:Add called
None
.decorate..set_level at 0x000001E7508B08B0>
None
.decorate..set_message at 0x000001E7508B0B80>
None
.decorate..set_level at 0x000001E7508B0F70>
None
.decorate..set_message at 0x000001E7508C5280>
Spam!
4
  1. 将装饰器定义为类的一部分
# 你想在类中定义装饰器,并将其作用在其他函数或方法上。
# 在类里面定义装饰器很简单,但是你首先要确认它的使用方式。比如到底是作为一个实例方法还是类方法
from functools import wraps

class A:
    def decorator1(self, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 1')
            return func(*args, **kwargs)
        return wrapper

    @classmethod
    def decorator2(cls, func):
        @wraps(func)
        def wrapper(*args, **kwargs):
            print('Decorator 2')
            return func(*args, **kwargs)
        return wrapper
    
a = A()
@a.decorator1
def spam():
    pass
# As a class method
@A.decorator2
def grok():
    pass
  1. 将装饰器定义为类
# 带参数的类的装饰器
import types
from functools import wraps


class Profiled:
    def __init__(self, func):
        wraps(func)(self)  # 完成元数据恢复
        self.ncalls = 0

    def __call__(self, *args, **kwargs):
        self.ncalls += 1
        return self.__wrapped__(*args, **kwargs)  # 用__wrapped__指向原函数

    def __get__(self, instance, cls):  
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)  #type.MethodType() 手动创建一个绑定方法来使用。只有当实例被使用的时候绑定方法才会被创建。如果这个方法是在类上面来访问,那么 __get__() 中的instance参数会被设置成None并直接返回 Profiled 实例本身。这样的话我们就可以提取它的 ncalls 属性了


@Profiled
def add(x, y):
    return x + y

class Spam:
    @Profiled
    def bar(self, x):
        print(self, x)


print(add(1, 3))
print(add(2, 3))
print(add)  #  <__main__.Profiled object at 0x00000191365C4E88> 
print(add.ncalls)
s=Spam()  # 如果注释上面的 __get__() 方法 会报错。 原因是当方法函数在一个类中被查找时,它们的 __get__() 方法依据描述器协议被调用,在8.9小节已经讲述过描述器协议了。在这里,__get__() 的目的是创建一个绑定方法对象(最终会给这个方法传递self参数)

console:
4
5
<__main__.Profiled object at 0x000001F3638CA1F0>
2
  1. 为类和静态方法提供装饰器
def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        print(end-start)
        return r
    return wrapper
# 注意顺序,如果@classmethod和@timethis写反 就会出错
class Spam:
    @timethis
    def instance_method(self, n):
        print(self, n)
        while n > 0:
            n -= 1

    @classmethod
    @timethis
    def class_method(cls, n):
        print(cls, n)
        while n > 0:
            n -= 1

    @staticmethod
    @timethis
    def static_method(n):
        print(n)
        while n > 0:
            n -= 1

numpy pandas

  1. numpy
import numpy as np
np.empty([3,4],dtype=int)
console:
array([[0, 0, 0, 0],
       [0, 0, 0, 0],
       [0, 0, 0, 0]])

a=[(1,2,3),(4,5,6)]
np.asarray(a)
console:
array([[1, 2, 3],
       [4, 5, 6]])

np.random.random([3,4])
np.linspace(start=0,stop=20,num=11) # 创建一维等差数组
np.tile(a,(2,3)) # 矩阵扩展

  1. pandas
import pandas as pd
df2 = pd.DataFrame({'A': [1, 2, 5, 3],
                    'B': pd.date_range('20190101', periods=4),
                    'C': pd.Series(1, index=range(4)),
                    'D': pd.array([3] * 4),
                    'E': pd.Categorical(["test", "train", "test", "22"]),
                    "F": "foo"
                    })

# 通过序列创建
import pandas as pd;pd.DataFrame(pd.Series([2,3,4,5]),columns=["Series"])
# 通过序列对象创建
import pandas as pd ;import numpy as np; pd.DataFrame({
    "A":pd.Series(12),
    "B":pd.Series(11,index=range(4)),
    "C":pd.date_range("2020-1-1",periods=4),
    "D":"D",
    "T":pd.Timestamp("20200101")
})
# 转换为
import pandas as pd ;import numpy as np; pd.DataFrame(np.array([[1,2],[3,4],[5,6]]),index=range(1,4),columns=["A","B"])
# 读和写文件
import pandas as pd; pd.read_excel("C:\\Users\\YCKJ2518\\Desktop\\jiaju.xlsx", sheet_name="Sheet2")
# pd.to_excel("data.xlsx".sheet_name="Sheet1")

a.describe() # 统计概要
a.head(1) # 头部数据预览
a.tail(1) # 尾部
a.index
a.columns
a.sort_index(axis=1,ascending=False) # 按轴排序
a.mean() # 获取每一列的平均值
a.mean(1) # 获取每一行的平均值
# 数据转换
# data.dtypes 查看数据类型
# data['age'].astype(str)  # 转换为字符串
# data['price'].str[1:].astype(float) #  将  ¥3.33 转换为  3.33 
# pd.to_datetime(data['birthday'],format='%y-%m-%d')  # 字符串转换为日期类型
# 小写 data['genger'].str.lower() 
#处理重复数据, 
#判断是否重复:data.duplicated()  data.duplicated('A')  
#删除重复数据:data.drop_duplicates()  data.duplicates(['A'])  
#数据替换 data.replace(999,'-1')
# 使用函数分组
# 当函数作为键时,会对每个索引值调用一次函数,同时返回值会被用作分组名称
print(people.groupby(len).sum())
people.groupby(len,axis=1).sum()
# dataframe 可以在按行方向(axis=0)和按列方向(axis=1) 进行分组 , 默认=0
# 类似于join on    pd.merge(data1,data2,on='id',how='left')   left_on,right_on,
# 也可以用 data1.join(data2)
# pd.concat() 合并
# unstack() 行旋转为列
# stack() 列旋转为行
# pd.melt() 多列合并为一列,指定一列作为分组指标,其他列都是对应的数据值
# data.pivot() # 先set_dindex创建层次化索引后,在用unstack()对数据重塑 , 类似于铺展开个矩阵

你可能感兴趣的:(Python知识点)