欠4前年的一份笔记 ,献给今后的自己。

魔术方法

特殊属性

在这里插入图片描述

查看属性

在这里插入图片描述

如果dir(lobji)参数obj包含方法 __dir__(),该方法将被调用。如果参数obj不包含__dir__(),
该方法将最大限度地收集参数信息。
dir()对于不同类型的对象具有不同的行为:
如果对象是模块对象,返回的列表包含模块的属性名。
如果对象是类型或者类对象,返回的列表包含类的属性名,及它的基类的属性名。
否则,返回列表包含对象的属性名,它的类的属性名和类的基类的属性名。

animal. py
class Animal:x = 123def __init__(self, name):self._name = nameself._age = 10self.weight = 20
test.py
print('animal Modulel\'s names ={}'.format(dir()))  # 模块的属性# cat.py
import animalfrom animal import Animalclass Cat(Animal):x = 'cat'y = 'abcd'class Dog(Animal):def _dir_(self):return ['dog']  # 必须返回可迭代对象print('-' * 30)print('Current Modulel\'s names = {}'.format(dir()))  # 模块名词空间内的属性
print('animal Modulel\'s names = {}'.format(dir(animal)))  # 指定模块名词空间内的属性print("object's __dict__        ={} ".format(sorted(object.__dict__.keys())))  # object的字典print("Animal's dir() = {} ".format(dir(Animal)))  # *Animal的dir()print("Cat's dir() = {}".format(dir(Cat)))  # CatHJdir()print('~' * 50)tom = Cat('tome')print(sorted(dir(tom)))  # 实例tom的属性、Cat类及所有祖先类的类属性print(sorted(tom.__dict__))  # 同上# dir()的等价 近似如下,_dict_字典中几乎包括了所有属性print(sorted(set(tom.__dict__.keys()) | set(Cat.__dict__.keys()) | set(object.__dict__.keys())))print("Dog's dir = {}".format(dir(Dog)))dog = Dog('snoppy')print(dir(dog))print(dog.__dict__)
输出:animal Modulel's names =['__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
------------------------------
Current Modulel's names = ['Animal', 'Cat', 'Dog', '__annotations__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__', 'animal']
animal Modulel's names = ['Animal', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
object's __dict__        =['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__'] 
Animal's dir() = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'x'] 
Cat's dir() = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'x', 'y']
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_age', '_name', 'weight', 'x', 'y']
['_age', '_name', 'weight']
['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '_age', '_name', 'weight', 'x', 'y']
Dog's dir = ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_dir_', 'x']
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', '_age', '_dir_', '_name', 'weight', 'x']
{'_name': 'snoppy', '_age': 10, 'weight': 20}

魔术方法**

  • 分类:
    1、创建、初始化与销毁
    __init__ 与 __del__
    2、 hash
    3、 bool
    4、 可视化
    5、 运算符重载
    6、 容器和大小
    7、 可调用对象
    8、上下文管理
    9、 反射
    10、 描述器
    11、 其他杂项

hash

在这里插入图片描述

class A:def __init__(self, name, age=18):self.name = namedef __hash__(self):return 1def __repr__(self):return self.nameprint(hash(A('tom')))               # 1print((A('tom'), A('tom'))) #(tom, tom)print([A('tom'), A('tom')])     # [tom, tom]print('~'*30)s = {A('tom'), A('tom')}  # setprint(s)  # 去重了吗        {tom, tom}print({tuple('t'), tuple('t')})     # {('t',)}print({('tom',), ('tom',)})     # {('tom',)}print({'tom', 'tom'})       # {'tom'}

上例中set为什么不能剔除相同的key?

class A:def __init__(self, name, age=18):self.name = namedef __hash__(self):return 1def __repr__(self):return self.namedef __eq__(self, other):  # 这个函数作用?return self.name == other.nameprint(hash(A('tom')))  # 1print((A('tom'), A('tom')))  # (tom, tom)print([A('tom'), A('tom')])  # [tom, tom]print('~' * 30)s = {A('tom'), A('tom')}  # setprint(s)  # [去重了吗 ] diff      {tom}print({tuple('t'), tuple('t')})  # {('t',)}print({('tom',), ('tom',)})  # {('tom',)}print({'tom', 'tom'})  # {'tom'}

在这里插入图片描述

__hash__ 方法只是返回一个hash值作为set的key,但是 去重,还需要__eq__来判断2个对象

是否相等。
hash值相等,只是hash冲突,不能说明两个对象是相等的。
因此,一般来说提供 __hash__ 方法是为了作set或者dict的key,所以 去重 要同时提供
__eq__ 方法。
不可hash对象isinstance(p1, collections.Hashable)一定为False。
去重 需要提供__eq__方法。

思考:

list类实例为什么不可hash?

练习
设计二维坐标类Point,使其成为可hash类型,并比较2个坐标的实例是否相等?
list类实例为什么不可hash
源码中有一句__hash__ =None,也就是如果调用 __hash__相当于None(),一定报错。
所有类都继承object ,而这个类是具有__hash__方法的,如果一个类不能被hash,就把
__hash__ 设置为None。

练习参考

from collections import Hashableclass Point:def __init__(self, x, y):self.x = xself.y = ydef __hash__(self):return hash((self.x, self.y))def __eq__(self, other):return self.x == other.x and self.y == other.yp1 = Point(4, 5)
p2 = Point(4, 5)print(hash(p1))print(hash(p2))print(p1 is p2)print(p1 == p2)  # True 使用_eq_print(hex(id(p1)), hex(id(p2)))print(set((p1, p2)))print(isinstance(p1, Hashable))

bool

class A: passprint(bool(A()))if A():print('Real A')class B:def __bool__(self):return Falseprint(bool(B))
print(bool(B()))if B:print('Real B')class C:def _len_(self):return 0print(bool(C()))if C():print('Real C')
输出:
True
Real A
True
False
Real B
True
Real C

可视化

在这里插入图片描述

class A:def __init__(self, name, age=18):self.name = nameself.age = agedef __repr__(self):return 'repr: {},{}'.format(self.name, self.age)def __str__(self):return 'str: {},{}'.format(self.name, self.age)def __bytes__(self):# return "(} is {}". format(self.name, self.age).encode()import jsonreturn json.dumps(self.__dict__).encode()print(1, A('tom'))  # print函数使用__str__print('2', [A('tom')])  # []使用_str_,但其内部使用_repr_print(3, ([str(A('tom'))]))  # []使用_str_,str()函数也使用_str_print(4, A('tom'))  # print函数使用_str_
print(5, [A('tom')])  # []使用_str_,但其内部使用_repr_
print(6, ([str(A('tom'))]))  # []使用_str ,str()函数也使用_str
print(7, 'str:a,1')  # 字符串直接输出没有引号
s = '1'
print(8, s)
print(9, ['a'], (5,))  # 字符串在基本数据类型内部输出有引号
print(10, {s, 'a'})
print(11, bytes(A('tom')))输出:
1 str: tom,18
2 [repr: tom,18]
3 ['str: tom,18']
4 str: tom,18
5 [repr: tom,18]
6 ['str: tom,18']
7 str:a,1
8 1
9 ['a'] (5,)
10 {'1', 'a'}
11 b'{"name": "tom", "age": 18}'

运算符重载

operator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作

在这里插入图片描述

class A:def __init__(self, name, age=18):self.name = nameself.age = agedef __sub__(self, other):return self.age - other.agedef __isub__(self, other):return A(self.name, self - other)tom = A('tom')
jerry = A('jerry', 16)print(1, tom - jerry)  # 2 print(2, jerry - tom, jerry.__sub__(tom))  # -2 , -2 nprint(3, id(tom))  # 4378826064
tom -= jerry
print(4, tom.age, id(tom))  # 2 4381561616输出:
1 2
2 -2 -2
3 4378826064
4 2 4381561616

练习:
完成Point类设计,实现判断点相等的方法,并完成向量的加法

在直角坐标系里面,定义原点为向量的起点.两个向量和与差的坐标分别等于这两个向量相应坐标
的和与差若向量的表示为(xy)形式,

A(X1, Y1) B(X2,Y2), JWJA+B= (X1+X2, Y1+Y2) , A-B= (X1-X2, Y1-Y2)

class Point:def __init__(self, x, y):self.x = xself.y = ydef __eq__(self, other):return self.x == other.x and self.y == other.ydef __add__(self, other):return Point(self.x + other.x, self.y + other.y)def add(self, other):return (self.x + other.x, self.y + other.y)def __str__(self):return '<Point: {}, {}>'.format(self.x, self.y)p1 = Point(1, 1)
p2 = Point(1, 1)
points = (p1, p2)print(points[0].add(points[1]))  # (2, 2)# 运算符重载
print(points[0] + points[1])  # <Point: 2, 2>
print(p1 == p2)  # True

运算符重载应用场景

往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。例如,上例中的对+进行了运算符重载,实现了Point类的二元操作,重新定义为Point+Point。

提供运算符重载,比直接提供加法方法要更加适合该领域内使用者的习惯。int类,几乎实现了所有操作符,可以作为参考。

@functools.total_ordering 装饰器

在这里插入图片描述

from functools import total_ordering@total_ordering
class Person:def __init__(self, name, age):self.name = nameself.age = agedef __eq__(self, other):return self.age == other.agedef __gt__(self, other):return self.age > other.agetom = Person('tom', 20)
jerry = Person('jerry', 16)print(tom > jerry)  # Trueprint(tom < jerry)  # Falseprint(tom >= jerry)  #True
print(tom <= jerry) # False

上例中大大简化代码,但是一般来说比较实现等于或者小于方法也就够了,其它可以不实现,所以这个装饰器只是看着很美好,且可能会带来性能问题,建议需要什么方法就自己创建,少用这个装饰器。

class Person:def __init__(self, name, age):self.name = nameself.age = agedef __eq__(self, other):return self.age == other.agedef __gt__(self, other):return self.age > other.agedef __ge__(self, other):return self.age >= other.agetom = Person('tom', 20)
jerry = Person('jerry', 16)print(tom > jerry)  # True
print(tom < jerry)  # False
print(tom >= jerry)  # True
print(tom <= jerry)  # False
print(tom == jerry)  # False
print(tom != jerry)  # True

容器相关方法

在这里插入图片描述

class A(dict):def __missing__(self, key):print('Missing key : ', key)return 0a = A()
print(a['k'])输出:
Missing key :  k
0

思考
为什么空字典、空字符串、空元组、空集合、空列表等可以等效为False?
练习
将购物车类改造成方便操作的容器类

class Cart:def __init__(self):self.items = []def __len__(self):return len(self.items)def additem(self, item):self.items.append(item)def __iter__(self):return iter(self.items)def __getitem__(self, index):  # 索引访问return self.items[index]def __setitem__(self, key, value):  # 索引赋值self.items[key] = valuedef __str__(self):return str(self.items)def __add__(self, other):  # +self.items.append(other)return selfcart = Cart()cart.additem(1)
cart.additem('abc')
cart.additem(3)# 长度、bool
print(len(cart))
print(bool(cart))
# 迭代
for x in cart:print(x)
# in
print(3 in cart)
print(2 in cart)
# 索引操作
print(cart[1])
cart[1] = 'xyz'
print(cart)
# # 链式编程实现加法
print(cart + 4 + 5 + 6)
print(cart.__add__(17).__add__(18))
输出:
3
True
1
abc
3
True
False
abc
[1, 'xyz', 3]
[1, 'xyz', 3, 4, 5, 6]
[1, 'xyz', 3, 4, 5, 6, 17, 18]

可调用对象

Python中一切皆对象,函数也不例外。

def foo():print(foo.__module__, foo.__name__)foo()
# 等价于
foo.__call__()
输出:
__main__ foo
__main__ foo

函数即对象,对象foo加上(),就是调用对象的__call__()方法
可调用对象

在这里插入图片描述可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。

class Point:def __init__(self, x, y):self.x = xself.y = ydef __call__(self, *args, **kwargs):return "<Point {}: {}>".format(self.x, self.y)p = Point(4, 5)print(p)    # <__main__.Point object at 0x104b7d550>
print(p())  # <Point 4: 5>class Adder:def __call__(self, *args):ret = 0for x in args:ret += xself.ret = retreturn retadder = Adder()
print(adder(4, 5, 6))   # 15
print(adder.ret)    #15

练习:
定义一个斐波那契数列的类,方便调用,计算第n项

class Fib:def __init__(self):self.items = [0, 1, 1]def __call__(self, index):if index < 0:raise IndexError('Wrong Index')if index < len(self.items):return self.items[index]for i in range(3, index + 1):self.items.append(self.items[i - 1] + self.items[i - 2])return self.items[index]print(Fib()(100))	# 354224848179261915075

上例中,增加迭代的方法、返回容器长度、支持索引的方法

class Fib:def __init__(self):self.items = [0, 1, 1]def __call__(self, index):return self[index]def __iter__(self):return iter(self.items)def __len__(self):return len(self.items)def __getitem__(self, index):if index < 0:raise IndexError('Wrong Index')if index < len(self.items):return self.items[index]for i in range(len(self), index + 1):self.items.append(self.items[i - 1] + self.items[i - 2])return self.items[index]def __str__(self):return str(self.items)_repr_ = __str__fib = Fib()print(fib(5), len(fib))  # 全部计算
print(fib(10), len(fib))  # 部分计算
for x in fib:print(x)
print(fib[5], fib[6])  # 索引访问,不计算输出:
5 6
55 11
0
1
1
2
3
5
8
13
21
34
55
5 8

可以看出使用类来实现斐波那契数列也是非常好的实现,还可以缓存数据,便于检索。

上下文管理

文件IO操作可以对文件对象使用上下文管理,使用with…as语法。

with open('test') as f:pass

仿照上例写一个自己的类,实现上下文管理

class Point:passwith Point() as p:  # AttributeError: _exit_pass

__exit__ 提示属性错误,没有 ,看了需要这个属性

上下文管理对象

当一个对象同时实现了__enter__()和__exit__方法,它就属于上下文管理的对象

在这里插入图片描述

class Point:def __init__(self):print('init')def __enter__(self):print('enter')def __exit__(self, exc_type, exc_val, exc_tb):print('exit')with Point() as f:print('do sth.')

实例化对象的时候,并不会调用enter,进入with语句块调用__enter__ 方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法。with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。

上下文管理的安全性

看看异常对上下文的影响。

class Point:def __init__(self):print('init')def __enter__(self):print('enter')def __exit__(self, exc_type, exc_val, exc_tb):print('exit')with Point() as f:raise Exception('error')print('do sth.')
输出:
Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 13, in <module>raise Exception('error')
Exception: error
init
enter
exit

可以看出在enter和exit照样执行,上下文管理是安全的。
极端的例子
调用sys.exit(),它会退出当前解释器。

打开Python解释器,在里面敲入sys.exit(),窗口直接关闭了。也就是说碰到这一句,Python运行环境直接退出了。

import sysclass Point:def __init__(self):print('init')def __enter__(self):print('enter')def __exit__(self, exc_type, exc_val, exc_tb):print('exit')with Point() as f:sys.exit(-100)print('do sth.')print('outer')
输出:
init
enter
exit

从执行结果来看,依然执行了__exit__函数,哪怕是退出Python运行环境。说明上下文管理很安全。

with语句

class Point:def __init__(self):print('init')def __enter__(self):print('enter')def __exit__(self, exc_type, exc_val, exc_tb):print('exit')p = Point()
with p as f:print(p == f)  # 为什么不相等
print('do sth. ')
输出:
init
enter
False
exit
do sth. 

问题在于 __enter__方法上,它将自己的返回值赋给f。修改上例

class Point:def __init__(self):print('init')def __enter__(self):print('enter')return selfdef __exit__(self, exc_type, exc_val, ex_tb):print('exit')p = Point()
with p as f:print(p == f)
print('do sth.')输出:
init
enter
True
exit
do sth.

__enter__ 方法返回值就是上下文中使用的对象,with语法会把它的返回值赋给as子句的变量。

方法和 __exit__ 方法的参数 __exit__ 方法参数
__enter__ 方法没有其他参数。
__exit__ 方法有3个参数:
__exit__(self, exc_type, exc_value, traceback)
这三个参数都与异常有关。
如果该上下文退出时没有异常,这3个参数都为None。
如果有异常,参数意义如下
exc_type,异常类型
exc_value,异常的值
traceback,异常的追踪信息
__exit__ 方法返回一个等效True的值,则压制异常;否则,继续抛出异常

class Point:def __init__(self):print('init')def __enter__(self):print('enter')return selfdef __exit__(self, exc_type, exc_val, exc_tb):print(exc_type)print(exc_val)print(exc_tb)print('exit')return "abc"p = Point()
with p as f:raise Exception('New Error')
print('do sth.')
print('outer')
输出:
init
enter
<class 'Exception'>
New Error
<traceback object at 0x1028b7e00>
exit
do sth.
outer

练习
为加法函数计时
方法1、使用装饰器显示该函数的执行时长
方法2、使用上下文管理方法来显示该函数的执行时长

import timedef add(x, y):time.sleep(2)return x + y

装饰器实现

import datetime
from functools import wraps
import timedef timeit(fn):@wraps(fn)def wrapper(*args, **kwargs):start = datetime.datetime.now()ret = fn(*args, **kwargs)delta = (datetime.datetime.now() - start).total_seconds()print('{} took {}s'.format(fn.__name__, delta))return retreturn wrapper@timeit
def add(x, y):time.sleep(2)return x + yprint(add(4, 5))        
输出:
add took 2.000806s
9

用上下文实现

import datetime
from functools import wraps
import timedef add(x, y):time.sleep(2)return x + yclass Timeit:def __init__(self, fn):self.fn = fndef __enter__(self):self.start = datetime.datetime.now()return self.fndef __exit__(self, exc_type, exc_val, exc_tb):delta = (datetime.datetime.now() - self.start).total_seconds()print("{} took {}s".format(self.fn.__name__, delta))with Timeit(add) as fn:print(fn(4, 6))print('~' * 80)print(add(4, 5))

另一种实现,使用可调用对象实现。

import datetime
from functools import wraps
import timedef add(x, y):time.sleep(2)return x + yclass Timeit:def __init__(self, fn):self.fn = fndef __enter__(self):self.start = datetime.datetime.now()return self.fndef __exit__(self, exc_type, exc_val, exc_tb):delta = (datetime.datetime.now() - self.start).total_seconds()print("{} took {}s".format(self.fn.__name__, delta))def __call__(self, x, y):print(x, y)return self.fn(x, y)with Timeit(add) as fn:print(fn(4, 5))输出:
9
add took 2.005119s

根据上面的代码,能不能把类当做装饰器用?

import datetime
from functools import wraps
import timeclass Timeit:def __init__(self, fn):self.fn = fndef __enter__(self):self.start = datetime.datetime.now()return self.fndef __exit__(self, exc_type, exc_val, exc_tb):delta = (datetime.datetime.now() - self.start).total_seconds()print("__exit__ {} took {}s".format(self.fn.__name__, delta))passdef __call__(self, *args, **kwargs):self.start = datetime.datetime.now()ret = self.fn(*args, **kwargs)self.delta = (datetime.datetime.now() - self.start).total_seconds()print('__call__ {} took {}s. call'.format(self.fn.__name__, self.delta))return ret@Timeit
def add(x, y):"""This is add function."""time.sleep(2)return x + yadd(1, 2)print(add.__doc__)
输出:
__call__ add took 2.00509s. call
None

思考
如何解决文档字符串问题?
方法一
__doc__ 直接修改

class Timeit:def __init__(self, fn):self.fn = fn#把函数对象的文档字符串赋给类self.__doc__ = fn.__doc__

方法二
使用functools.wraps函数

import datetime
from functools import wraps
import timeclass Timeit:"""This is A Class"""def __init__(self, fn):self.fn = fn# 把函数对象的文档字符串赋给类# self._doc_ = fn._doc_# update_wrapper(self, fn)wraps(fn)(self)def __enter__(self):self.start = datetime.datetime.now()return self.fndef __exit__(self, exc_type, exc_val, exc_tb):delta = (datetime.datetime.now() - self.start).total_seconds()print("__exit__ {} took {}s".format(self.fn.__name__, delta))passdef __call__(self, *args, **kwargs):self.start = datetime.datetime.now()ret = self.fn(*args, **kwargs)self.delta = (datetime.datetime.now() - self.start).total_seconds()print('__call__ {} took {}s. call'.format(self.fn.__name__, self.delta))return ret@Timeit
def add(x, y):"""This is add function."""time.sleep(2)return x + yadd(1, 2)print(add.__doc__)print(Timeit(add).__doc__)输出:
__call__ add took 2.004217s. call
This is add function.
This is add function.

上面的类即可以用在上下文管理,又可以用做装饰器

上下文应用场景

1.增强功能
在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。
2. 资源管理
打开了资源需要关闭,例如文件对象、网络连接、数据库连接等
3. 权限验证
在执行代码之前,做权限的验证,在__enter__中处理

contextlib.contextmanager

它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现 __enter__和
__exit__方法。
对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。

也就是这个装饰器接收一个生成器对象作为参数。

import contextlib@contextlib.contextmanager
def foo():  #print('enter')  # 相当于_enter_() 求学院yield  # yield 5, yield的值只能有一个,作为_enter_方法的返回值print('exit')  # 相当于_exit_() 人的高with foo() as f:# raise Exception()print(f)输出:
enter
None
exit

f接收yield语句的返回值。
上面的程序看似不错但是,增加一个异常试一试,发现不能保证exit的执行,怎么办?
try finally 。

import contextlib@contextlib.contextmanager
def foo():print('enter')try:yield  # yield 5,yield的值只能有一个,作为__enter_方法的返回值finally:print('exit')with foo() as f:raise Exception()
print(f)输出:
enter
exit
Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 15, in <module>raise Exception()
Exception

上例这么做有什么意义呢?
当yield发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。

  • 把yield之前的当做__enter__方法执行
  • 把yield之后的当做__exit__方法执行
  • 把yield的值作__enter__的返回值

总结
如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的
方式加__enter__和__exit__ 方法方便。

import contextlib
import datetime
import time@contextlib.contextmanager
def add(x, y):  # 为生成器函数增加了上下文管理start = datetime.datetime.now()try:yield x + y  # yield 5, yield的值只能有一个,作__enter__方法的返回值finally:delta = (datetime.datetime.now() - start).total_seconds()print(delta)with add(4, 5) as f:# raise Exception()time.sleep(2)print(f)
输出:
9
2.005053

总结
如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式,如果业务复杂,用类的
方式加 __enter__ 和 __exit__ 方法方便。

反射

概述
运行时,区别于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection,指的是运行时获取类型定义信息。
一个对象能够在运行时,像照镜了一样,反射出其类型信息。
简单说,在Python中,能够通过一个对象,找出其type、class. attribute或method的能力,称为反射或者自省。
具有反射能力的函数有:type()、isinstance()、callable()、dir()、getattr()

反射相关的函数和方法

需求

有一个Point类,查看它实例的属性,并修改它。动态为实例增加属性

class Point:def __init__(self, x, y):self.x = xself.y = ydef __str__(self):return "Point({}, {})".format(self.x, self.y)def show(self):print(self.x, self.y)p = Point(4, 5)
print(p)    #Point(4, 5)
print(p.__dict__)   #{'x': 4, 'y': 5}p.__dict__['y'] = 16    
print(p.__dict__)  # {'x': 4, 'y': 16}p.z = 10    
print(p.__dict__)   #{'x': 4, 'y': 16, 'z': 10}
print(dir(p))  # ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__firstlineno__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__static_attributes__', '__str__', '__subclasshook__', '__weakref__', 'show', 'x', 'y', 'z']
print(p.__dir__())  # ['x', 'y', 'z', '__module__', '__firstlineno__', '__init__', '__str__', 'show', '__static_attributes__', '__dict__', '__weakref__', '__doc__', '__new__', '__repr__', '__hash__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__reduce_ex__', '__reduce__', '__getstate__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']

上例通过属性字典__dict__来访问对象的属性,本质上也是利用的反射的能力。

但是,上面的例子中,访问的方式不优雅,Python提供了内置的函数。

在这里插入图片描述

用上面的方法来修改上例的代码

class Point:def __init__(self, x, y):self.x = xself.y = ydef __str__(self):return "Point({}, {})".format(self.x, self.y)def show(self):print(self)p1 = Point(4, 5)
p2 = Point(10, 10)print(1, repr(p1), repr(p2), sep=',')  # <__main__.Point object at 0x103099550>,<__main__.Point object at 0x103295450>print(p1.__dict__)  # {'x': 4, 'y': 5}setattr(p1, 'y', 16)
setattr(p1, 'z', 10)print(2, getattr(p1, '__dict__'))  # {'x': 4, 'y': 16, 'z': 10}# 动态调用方法
if hasattr(p1, 'show'):getattr(p1, 'show')()   # Point(4, 16)# 动态增加方法
# 为类增加方法
if not hasattr(Point, 'add'):setattr(Point, 'add', lambda self, other: Point(self.x + other.x, self.y + other.y))print(3, Point.add)  # <function <lambda> at 0x1003244a0>print(p1.add)           # <bound method <lambda> of <__main__.Point object at 0x100485550>>
print(5,p1.add(p2))  # 绑定     # Point(14, 26)# 为实例增加方法,未绑定
if not hasattr(p1, 'sub'):setattr(p1, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
print(6,p1.sub(p1, p1))   #Point(0, 0)
print(p1.sub)       # <function <lambda> at 0x100ddd080>
# add在谁里面,sub在谁里面
print(p1.__dict__)      # {'x': 4, 'y': 16, 'z': 10, 'sub': <function <lambda> at 0x100ddd080>}print(Point.__dict__)   #{'__module__': '__main__', '__firstlineno__': 1, '__init__': <function Point.__init__ at 0x100b8d440>, '__str__': <function Point.__str__ at 0x100c98040>, 'show': <function Point.show at 0x100d5b9c0>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None, 'add': <function <lambda> at 0x100c544a0>}

涔吮 思考
这种动态增加属性的方式和装饰器修饰一个类、Mixin方式的差异?
这种动态增删属性的方式是运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了,因此反射能力具有更大的灵活性。
练习
命令分发器,通过名称找对应的函数执行。
思路:名称找对象的方法

class Dispatcher:def __init__(self):self._run()def cmd1(self):print("I'm cmd1")def cmd2(self):print("I'm cmd2")def _run(self):while True:cmd = input('Plz input a command: ').strip()if cmd == 'quit':breakgetattr(self, cmd, lambda: print('Unknown Command {}'.format(cmd)))()Dispatcher()输出Plz input a command: cmd1
I'm cmd1
Plz input a command: cmd2
I'm cmd2
Plz input a command:

上例中使用getatt 方法找到对象的属性的方式,比自己维护一个字典来建立名称和函数之间的关
系的方式好多了。

反射相关的魔术方法

__getattr__(), __setattr__(),__delattr__()这三个魔术方法,分别测试

__getattr__()
class Base:n = 0class Point(Base):z = 6def __init__(self, x, y):self.x = xself.y = ydef show(self):print(self.x, self.y)def __getattr__(self, item):return "missing {}".format(item)p1 = Point(4, 5)
print(p1.x) # 4
print(p1.z) # 6
print(p1.n) # 0
print(p1.t)  # missing  t

一个类的属性会按照继承关系找,如果找不到,就会执行 __getattr__()方法,如果没有这个方法,就会抛出AttributeError异常表示找不到属性。

查找属性顺序为:
instance. __dict__ -->instance. __class__.__dict__ -->继承的祖先类(直到object)的__dict__
---找不到-->调用 __getattr__()

__setattr__()
class Base:n = 0class Point(Base):z = 6def __init__(self, x, y):self.x = xself.y = ydef show(self):print(self.x, self.y)def __getattr__(self, item):return "missing {}".format(item)def __setattr__(self, key, value):print("setattr {}={}".format(key, value))p1 = Point(4, 5)print(p1.x)  
print(p1.z)
print(p1.n)
print(p1.t)  # missingp1.x = 50
print(p1.__dict__)
p1.__dict__['x'] = 60
print(p1.__dict__)
print(p1.x)
输出:
setattr x=4
setattr y=5
missing x
6
0
missing t
setattr x=50
{}
{'x': 60}
60

实例通过.点设置属性,如同self.x =x,就会调用 __setattr__(),属性要加到实例的__dict__中,就需要自己完成。

class Base:n = 0class Point(Base):z = 6def __init__(self, x, y):self.x = xself.y = ydef show(self):print(self.x, self.y)def __getattr__(self, item):return "missing (}".format(item)def __setattr__(self, key, value):print("setattr {}={}".format(key, value))self.__dict__[key] = value

__setattr__()方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己操作实例的__dict__。

__delattr__()
class Point:z = 5def __init__(self, x, y):self.x = xself.y = ydef __delattr__(self, item):print('Can not del {}'.format(item))p = Point(14, 5)del p.x     # Can not del xp.z = 15del p.z     # Can not del zdel p.z     # Can not del zprint(Point.__dict__)   # {'__module__': '__main__', '__firstlineno__': 1, 'z': 5, '__init__': <function Point.__init__ at 0x1048a8040>, '__delattr__': <function Point.__delattr__ at 0x1047ef2e0>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
print(p.__dict__)       # {'x': 14, 'y': 5, 'z': 15}del Point.z
print(Point.__dict__)   # {'__module__': '__main__', '__firstlineno__': 1, '__init__': <function Point.__init__ at 0x1048a8040>, '__delattr__': <function Point.__delattr__ at 0x1047ef2e0>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}

可以阻止通过实例删除属性的操作。但是通过类依然可以删除属性。

__getattribute__
class Base:n = 0class Point(Base):z = 6def __init__(self, x, y):self.x = xself.y = ydef __getattr__(self, item):return "missing {}".format(item)def __getattribute__(self, item):return itemp1 = Point(4, 5)
print(p1.__dict__)  # __dict__
print(p1.x)  # x 
print(p1.z)  # z 
print(p1.n)  # n 
print(p1.t)  # t 
print(Point.__dict__)  # {'__module__': '__main__', '__firstlineno__': 7, 'z': 6, '__init__': <function Point.__init__ at 0x100880040>, '__getattr__': <function Point.__getattr__ at 0x1009479c0>, '__getattribute__': <function Point.__getattribute__ at 0x100d21080>, '__static_attributes__': ('x', 'y'), '__doc__': None}
print(Point.z)  # 6

实例的所有的属性访问,第一个都会调用 getattribute_ 方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常。它的return值将作为属性查找的结果。如果抛出AttributeError异常,则会直接调用 __getattr__方法,因沩表示属性没有找到。

class Base:n = 0class Point(Base):z = 6def __init__(self, x, y):self.x = xself.y = ydef __getattr__(self, item):return "missing {}".format(item)def __getattribute__(self, item):# raise AttributeError ("Not Found")# pass# return self.__dict__[item]return object.__getattribute__(self, item)p1 = Point(4, 5)
print(p1.__dict__)  # {'x': 4, 'y': 5}
print(p1.x)  # 4
print(p1.z)  # 6
print(p1.n)  # 0 
print(p1.t)  # missing t
print(Point.__dict__)  # {'__module__': '__main__', '__firstlineno__': 5, 'z': 6, '__init__': <function Point.__init__ at 0x100eb0040>, '__getattr__': <function Point.__getattr__ at 0x100f739c0>, '__getattribute__': <function Point.__getattribute__ at 0x100ff5080>, '__static_attributes__': ('x', 'y'), '__doc__': None}
print(Point.z)  # 6

__getattribute__ 方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如 object.__getattribute__(self,name)。
注意,除非你明确地知道 __getattribute__ 方法用来做什么,否则不要使用它。
在这里插入图片描述

描述器 Descriptors

描述器的表现

方法签名如下
object.__get__(self, instance, owner)
object.__set__(self, instance, value)
object.__delete__(self, instance)
self指代当前实例,调用者
instance 是owner的实例
owner 是属性的所属的类
请思考下面程序的执行流程是什么?

class A:def __init__(self):self.a1 = 'a1'print('A.init')class B:x = A()def __init__(self):print('B.init')print('-' * 20)
print(B.x.a1)
print('=' * 20)
b = B()print(b.x.a1)输出:
A.init
--------------------
a1
====================
B.init
a1

可以看出执行的先后顺序吧?
类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,所以打印
A.init。
然后执行到打印B.x.a1。
然后实例化并初始化B的实例b。
打印b.x.a1,会查找类属性b.x,指向A的实例,所以返回A实例的属性a1的值。
看懂执行流程了,再看下面的程序,对类A做一些改造。
如果在类A中实现 __get__ 方法,看看变化

class A:def __init__(self):self.a1 = 'a1'print('A.init')def __get__(self, instance, owner):print("A.__get__{} {} {}".format(self, instance, owner))class B:x = A()def __init__(self):print('B.init')print(' - ' * 20)
print(B.x)
# print(B.x.al) # #Uf#AttributeError: 'NoneType' object has no attribute 'al'
print('=' * 20)
b = B()print(b.x)输出:
A.init-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
A.__get__<__main__.A object at 0x102f61550> None <class '__main__.B'>
None
====================
B.init
A.__get__<__main__.A object at 0x102f61550> <__main__.B object at 0x103307620> <class '__main__.B'>
None

因为定义了__get__ 方法,类A就是一个描述器,对类B或者类B的实例的x属性读取,成为对类A的实例的访问,就会调用 __get__方法
如何解决上例中访问报错的问题,问题应该来自 __get__ 方法。
self, instance,owner这这三个参数,是什么意思?
<__main__.A object at 0x0000000000B84E48> None
<__main__.A object at 0x0000000000B84E48><__main__.B object at 0x0000000000B84F28>
self都是A的实例
owner都是B类
instance说明

- None表示是没有B类的实例,对应调用B.x
- <__main__.B object at 0x0000000000B84F28>表示是B的实例,对应调用B().×

使用返回值解决。返回self,就是A的实例,该实例有a1属性,返回正常。

class A:def __init__(self):self.a1 = 'a1'print('A.init')def __get__(self, instance, owner):print("A.__get__{} {} {}".format(self, instance, owner))return self  # 解决 返回  None 的问题class B:x = A()def __init__(self):print('B.init')print(' - ' * 20)print(B.x)print(B.x.a1)print('=' * 20)b = B()print(b.x)
print(b.x.a1)输出:
A.init-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
A.__get__<__main__.A object at 0x10084d550> None <class '__main__.B'>
<__main__.A object at 0x10084d550>
A.__get__<__main__.A object at 0x10084d550> None <class '__main__.B'>
a1
====================
B.init
A.__get__<__main__.A object at 0x10084d550> <__main__.B object at 0x100af7620> <class '__main__.B'>
<__main__.A object at 0x10084d550>
A.__get__<__main__.A object at 0x10084d550> <__main__.B object at 0x100af7620> <class '__main__.B'>
a1

那么类B的实例属性也可以?

class A:def __init__(self):self.a1 = 'a1'print('A.init')def __get__(self, instance, owner):print("A.__get__{} {} {}".format(self, instance, owner))return self  # 解决 返回  None 的问题class B:x = A()def __init__(self):print('B.init')self.b = A()print(' - ' * 20)print(B.x)print(B.x.a1)print('=' * 20)b = B()print(b.x)
print(b.x.a1)print('-----------------')print(b.b)输出:A.init-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
A.__get__<__main__.A object at 0x105301550> None <class '__main__.B'>
<__main__.A object at 0x105301550>
A.__get__<__main__.A object at 0x105301550> None <class '__main__.B'>
a1
====================
B.init
A.init
A.__get__<__main__.A object at 0x105301550> <__main__.B object at 0x105527620> <class '__main__.B'>
<__main__.A object at 0x105301550>
A.__get__<__main__.A object at 0x105301550> <__main__.B object at 0x105527620> <class '__main__.B'>
a1
-----------------
<__main__.A object at 0x1055bd1d0>

从运行结果可以看出,只有类属性是类的实例才行。

描述器定义

Python中,一个类实现了 __get__、__set__,__delete__ 三个方法中的任何一个方法,就是描述器。
如果仅实现了__get__,就是非数据描述符 non-data descriptor ;
同时实现了__get__、__set__ 就是数据描述符 data descriptor。
如果一个类的类属性设置为描述器,那么它被称为owner属主。

属性的访问顺序

为上例中的类B增加实例属性x

class A:def __init__(self):self.a1 = 'a1'print('A.init')def __get__(self, instance, owner):print("A.__get__{} {} {}".format(self, instance, owner))return self  # 解决 返回  None 的问题class B:x = A()def __init__(self):print('B.init')self.x = 'b.x' # 增加实例属性xprint(' - ' * 20)print(B.x)print(B.x.a1)print('=' * 20)b = B()print(b.x)# Traceback (most recent call last):
#   File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 30, in <module>
#     print(b.x.a1)
#           ^^^^^^
# AttributeError: 'str' object has no attribute 'a1'
# print(b.x.a1)输出:
A.init-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
A.__get__<__main__.A object at 0x104e49550> None <class '__main__.B'>
<__main__.A object at 0x104e49550>
A.__get__<__main__.A object at 0x104e49550> None <class '__main__.B'>
a1
====================
B.init
b.x

b.x访问到了实例的属性,而不是描述器。
继续修改代码,为类A增加 __set__ 方法。

class A:def __init__(self):self.a1 = 'a1'print('A.init')def __get__(self, instance, owner):print("A.__get__{} {} {}".format(self, instance, owner))return self  # 解决 返回  None 的问题def __set__(self, instance, value):print('A._ set_ {} {} {} '. format(self, instance, value))self.data = valueclass B:x = A()def __init__(self):print('B.init')self.x = 'b.x' # 增加实例属性xprint(' - ' * 20)print(1,B.x)    # 1 <__main__.A object at 0x1006f5550>print(2,B.x.a1) # a1print('=' * 20)b = B()print(10,b.x)  #  <__main__.A object at 0x102cd9550>print(b.x.a1)   # a1
输出:
A.init-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
A.__get__<__main__.A object at 0x1006f5550> None <class '__main__.B'>
1 <__main__.A object at 0x1006f5550>
A.__get__<__main__.A object at 0x1006f5550> None <class '__main__.B'>
2 a1
====================
B.init
A._ set_ <__main__.A object at 0x1006f5550> <__main__.B object at 0x1008af620> b.x 
A.__get__<__main__.A object at 0x1006f5550> <__main__.B object at 0x1008af620> <class '__main__.B'>
10 <__main__.A object at 0x1006f5550>
A.__get__<__main__.A object at 0x1006f5550> <__main__.B object at 0x1008af620> <class '__main__.B'>
a1

返回变成了a1,访问到了描述器的数据。
属性查找顺序
实例的__dict__ 优先于非数据描述器
数据描述器 优先于实例的 __dict__
__delete__ 方法有同样的效果,有了这个方法,就是数据描述器。
尝试着增加下面的代码,看看字典的变化

b.x = 500
B.x = 600
b.x= 500,这是调用数据描述器的__set__ 方法,或调用非数据描述器的实例覆盖。

B.X=600,赋值即定义,这是覆盖类属性。

本质(进阶)

Python真的会做的这么复杂吗,再来一套属性查找顺序规则?看看非数据描述器和数据描述器,
类B及其 __dict__的变化。
屏蔽和不屏蔽 __set__方法,看看变化。

class A:def __init__(self):self.a1 = 'a1'print('A.init')def __get__(self, instance, owner):print("A.__get__{} {} {}".format(self, instance, owner))return self  # 解决 返回  None 的问题#def __set__(self, instance, value):#    print('A._ set_ {} {} {} '. format(self, instance, value))#    self.data = valueclass B:x = A()def __init__(self):print('B.init')self.x = 'b.x'  # 增加实例属性xself.y = 'b.y'print(' - ' * 20)print(1, B.x)  # 1 <__main__.A object at 0x1006f5550>print(2, B.x.a1)  # a1print('=' * 20)b = B()print(b.y)  # a1
print('字典')
print(11, b.__dict__)
print(12, B.__dict__)不删除  __set__  方法结果如下  :def __set__(self, instance, value):print('A._ set_ {} {} {} '. format(self, instance, value))self.data = valueA.init-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
A.__get__<__main__.A object at 0x103141550> None <class '__main__.B'>
1 <__main__.A object at 0x103141550>
A.__get__<__main__.A object at 0x103141550> None <class '__main__.B'>
2 a1
====================
B.init
A._ set_ <__main__.A object at 0x103141550> <__main__.B object at 0x10377b620> b.x 
b.y
字典
11 {'y': 'b.y'}
12 {'__module__': '__main__', '__firstlineno__': 16, 'x': <__main__.A object at 0x103141550>, '__init__': <function B.__init__ at 0x103321300>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}删除 __set__ 方法结果如下 A.init-  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
A.__get__<__main__.A object at 0x1024cd550> None <class '__main__.B'>
1 <__main__.A object at 0x1024cd550>
A.__get__<__main__.A object at 0x1024cd550> None <class '__main__.B'>
2 a1
====================
B.init
b.y
字典
11 {'x': 'b.x', 'y': 'b.y'}
12 {'__module__': '__main__', '__firstlineno__': 16, 'x': <__main__.A object at 0x1024cd550>, '__init__': <function B.__init__ at 0x1024f9080>, '__static_attributes__': ('x', 'y'), '__dict__': <attribute '__dict__' of 'B' objects>, '__weakref__': <attribute '__weakref__' of 'B' objects>, '__doc__': None}

原来不是什么 数据描述器 优先级高,而是把实例的属性从 dict 中给去除掉了,造成了该属性如果是数据描述器优先访问的假象。说到底,属性访问顺序从来就没有变过。

Python中的描述器

描述器在Python中应用非常广泛。

Python的方法(包括staticmethod () 和classmethod()都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。property(函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。

class A:@classmethoddef foo(cls):  # 非数据描述器pass@staticmethod  # 非数据描述器def bar():pass@property  # 数据描述器def z(self):return 5def getfoo(self):  # 非数据描述器return self.foodef __init__(self):  # 非数据描述器self.foo = 100self.bar = 200# self.z = 300
a = A()
print(a.__dict__)       # {'foo': 100, 'bar': 200}
print(A.__dict__)   # {'__module__': '__main__', '__firstlineno__': 1, 'foo': <classmethod(<function A.foo at 0x102235440>)>, 'bar': <staticmethod(<function A.bar at 0x102440040>)>, 'z': <property object at 0x102447f10>, 'getfoo': <function A.getfoo at 0x102585080>, '__init__': <function A.__init__ at 0x102585300>, '__static_attributes__': ('bar', 'foo'), '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}

foo、bar都可以在实例中覆盖,但是z不可以。

练习
  1. 实现StaticMethod装饰器,完成staticmethod装饰器的功能
# 类staticmethod装饰器
class StaticMethod:  # 怕冲突改名def __init__(self, fn):self._fn = fndef __get__(self, instance, owner):return self._fnclass A:@StaticMethod# stmtd = StaticMethod(stmtd)def stmtd():print('static method')A.stmtd()
A().stmtd()
输出:
static method
static method
  1. 实现ClassMethod装饰器,完成classmethod装饰器的功能
from functools import partial#  类classmethod装饰器
class ClassMethod:  # 怕冲突改名def __init__(self, fn):self._fn = fndef __get__(self, instance, owner):ret = self._fn(owner)return retclass A:@ClassMethod# clsmtd = ClassMethod(clsmtd)# 调用A.clsmtd()或者 A().c1smtd()def clsmtd(cls):print(cls.__name__)print(A.__dict__)A.clsmtd
print('~' * 80)
A.clsmtd()输出:
{'__module__': '__main__', '__firstlineno__': 14, 'clsmtd': <__main__.ClassMethod object at 0x104a35550>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
A
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A
Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 26, in <module>A.clsmtd()~~~~~~~~^^
TypeError: 'NoneType' object is not callable

A.cIsmtd()的意思就是None(),一定报错。怎么修改?
A.clsmtd()其实就应该是A.clsmtd(cls)(),应该怎么处理?
A.clsmeth = A.clsmtd (cls)
应该用partial函数

from functools import partial#  类classmethod装饰器
class ClassMethod:  # 怕冲突改名def __init__(self, fn):self._fn = fndef __get__(self, instance, cls):ret = partial(self._fn, cls)return retclass A:@ClassMethod# clsmtd = ClassMethod(clsmtd)# 调用A.clsmtd()或者 A().c1smtd()def clsmtd(cls):print(cls.__name__)print(A.__dict__)print(A.clsmtd)print('~' * 80)print(A.clsmtd())
输出:
{'__module__': '__main__', '__firstlineno__': 14, 'clsmtd': <__main__.ClassMethod object at 0x104941550>, '__static_attributes__': (), '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
functools.partial(<function A.clsmtd at 0x1048eb9c0>, <class '__main__.A'>)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
A
None
  1. 对实例的数据进行校验
class Person:def __init__(self, name:str, age:int):self.name = nameself.age = age

对上面的类的实例的属性name、age进行数据校验
思路

  1. 写函数,在 __init__ 中先检查,如果不合格,直接抛异常
  2. 装饰器,使用inspect模块完成
  3. 描述器
# 写函数检查
class Person:def __init__(self, name: str, age: int):params = ((name, str), (age, int))if not self.checkdata(params):raise TypeErrorself.name = nameself.age = agedef checkdata(self, params):for p, t in params:if not isinstance(p, t):return Falsereturn Truep = Person('tom', '20')输出:Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 19, in <module>p = Person('tom', '20')File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 6, in __init__raise TypeError
TypeError

这种方法耦合度太高。
装饰器的方式,前面写过类似的,这里不再赘述。
描述器方式
需要使用数据描述器,写入实例属性的时候做检查

class Typed:def __init__(self, name, type):self.name = nameself.type = typedef __get__(self, instance, owner):if instance is not None:return instance.__dict__[self.name]return selfdef __set__(self, instance, value):if not isinstance(value, self.type):raise TypeError(value)instance.__dict__[self.name] = valueclass Person:name = Typed('name', str)  # 不优雅age = Typed('age', int)  # 不优雅def __init__(self, name: str, age: int):self.name = nameself.age = agep = Person('tom', '20')输出:Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 26, in <module>p = Person('tom', '20')File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 23, in __init__self.age = age^^^^^^^^File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 13, in __set__raise TypeError(value)
TypeError: 20

代码看似不错,但是有硬编码,能否直接获取形参类型,使用inspect模块
先做个实验

params = inspect.signature(Person).parametersOrderedDict({'name': <Parameter "name: str">, 'age': <Parameter "age: int">})

看看返回什么结果
完整代码如下

class Typed:def __init__(self, name, type):self.name = nameself.type = typedef __get__(self, instance, owner):if instance is not None:return instance.__dict__[self.name]return selfdef __set__(self, instance, value):if not isinstance(value, self.type):raise TypeError(value)instance.__dict__[self.name] = valueimport inspectdef typeassert(cls):params = inspect.signature(cls).parametersprint(params)for name, param in params.items():print(param.name, param.annotation)if param.annotation != param.empty:  # 注入类属性setattr(cls, name, Typed(name, param.annotation))return cls@typeassert
class Person:# name = Typed('name',str) # 装饰器注入# age = Typed('age', int)def __init__(self, name: str, age: int):self.name = nameself.age = agedef __repr__(self):return "{} is {}".format(self.name, self.age)p = Person('tom', 20)
print(p)print('~' * 100)
p = Person('tom', '20')
输出:
OrderedDict({'name': <Parameter "name: str">, 'age': <Parameter "age: int">})
name <class 'str'>
age <class 'int'>
tom is 20
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 47, in <module>p = Person('tom', '20')File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 37, in __init__self.age = age^^^^^^^^File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 13, in __set__raise TypeError(value)
TypeError: 20

可以把上面的函数装饰器改为类装饰器,如何写?

class Typed:def __init__(self, type):self.type = typedef __get__(self, instance, owner):passdef __set__(self, instance, value):print('T.set', self, instance, value)if not isinstance(value, self.type):raise ValueError(value)import inspectclass TypeAssert:def __init__(self, cls):self.cls = cls  # 记录着被包装的Person类新算业params = inspect.signature(self.cls).parametersprint(params)for name, param in params.items():print(name, param.annotation)if param.annotation != param.empty:setattr(self.cls, name, Typed(param.annotation))  # 注入类属性print(self.cls.__dict__)def __call__(self, name, age):p = self.cls(name, age)  # 重新构建一个新的Person对象return p@TypeAssert
class Person:  # Person = TypeAssert(Person)# name = Typed (str)# age = Typed (int)def __init__(self, name: str, age: int):self.name = nameself.age = agep1 = Person('tom', 18)
print(id(p1))
print('='*100)p2 = Person('tom', 20)
print(id(p2))
print('~' * 100 )
p3 = Person('tom', '20')输出:OrderedDict({'name': <Parameter "name: str">, 'age': <Parameter "age: int">})
name <class 'str'>
age <class 'int'>
{'__module__': '__main__', '__firstlineno__': 33, '__init__': <function Person.__init__ at 0x1045c5580>, '__static_attributes__': ('age', 'name'), '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'name': <__main__.Typed object at 0x103d55550>, 'age': <__main__.Typed object at 0x103b65d10>}
T.set <__main__.Typed object at 0x103d55550> <__main__.Person object at 0x103d556a0> tom
T.set <__main__.Typed object at 0x103b65d10> <__main__.Person object at 0x103d556a0> 18
4359280288
====================================================================================================
T.set <__main__.Typed object at 0x103d55550> <__main__.Person object at 0x103b65e50> tom
T.set <__main__.Typed object at 0x103b65d10> <__main__.Person object at 0x103b65e50> 20
4357250640
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
T.set <__main__.Typed object at 0x103d55550> <__main__.Person object at 0x103b66490> tom
T.set <__main__.Typed object at 0x103b65d10> <__main__.Person object at 0x103b66490> 20
Traceback (most recent call last):File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 50, in <module>p3 = Person('tom', '20')File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 29, in __call__p = self.cls(name, age)  # 重新构建一个新的Person对象File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 40, in __init__self.age = age^^^^^^^^File "/Users/quyixiao/pp/python_lesson/function1/function15.py", line 11, in __set__raise ValueError(value)
ValueError: 20

练习

有一个无序序列[37,99, 73,48,47,40,40,25,99,511,请先排序并打印输出。
分别尝试插入20、40、41到这个序列中合适的位置,保证其有序。
思路

排序后二分查找到适当位置插入数值。

排序使用sorted解决,假设升序输出。

查找插入点,使用二分查找完成。

假设全长为n,首先在大致的中点元素开始和待插入数比较,如果大则和右边的区域的中点继续

比较,如果小则和左边的区域的中点进行比较,以此类推。直到中点就是

def insert_sort(orderlist, i):ret = orderlist[:]low = 0high = len(orderlist) - 1while low < high:mid = (low + high) // 2if orderlist[mid] < i:  #low = mid + 1  # 说明主大,右边,限制下限else:high = mid  # 说明主不大手,左边,限制上限print(low)  # 1ow为插入点ret.insert(low, i)return ret# 测试
r = []
for x in (40, 20, 41):r = insert_sort(r, x)
print(r)输出:
0
0
1
[20, 41, 40]

看似上面代码不错,请测试插入100。
问题来了,100插入的位置不对,为什么?

def insert_sort(orderlist, i):ret = orderlist[:]low = 0high = len(orderlist)		# 去掉-1 while low < high:mid = (low + high) // 2if orderlist[mid] < i:  #low = mid + 1  # 说明i大,右边,限制下限else:high = mid  # 说明i不大于,左边,限制上限print(low)  # 1ow为插入点ret.insert(low, i)return ret# 测试
r = []
for x in (40, 20, 41):r = insert_sort(r, x)
print(r)
输出:
0
0
2
[20, 40, 41]

high = len(orderlist),去掉减1,不影响整除2,但影响下一行判断。
while low < high 这一句low索引可以取到length-1了,原来只能取到length-2,所以一旦插入元
素到尾部就出现问题了。算法的核心,就是折半至重合为止。

二分

二分前提是有序,否则不可以二分。
二分查找算法的时间复杂度o(logn)

bisect模块

Bisect模块提供的函数有:

  • bisect.bisect_left(a,x, lo=0, hi=len(a)) :
    查找在有序列表a中插入x的index。lo和hi用于指定列表的区间,默认是使用整个列表。如果x已经存在,在其左边插入。返回值为index。
  • bisect.bisect_right(a,x, lo=0, hi=len(a)) 或 bisect.bisect(a, x,lo=0, hi=len(a))和bisect_left类似,但如果x已经存在,在其右边插入。
  • bisect.insort_left(a,x, lo=0, hi=len(a))在有序列表a中插入x。等同于a.insert(bisect.bisect_left(a,x,lo,hi),x)。
    函数可以分2类:
    bisect系,用于查找index。
    Insort系,用于实际插入。
    默认重复时从右边插入。
import bisectlst = [37, 99, 73, 48, 47, 40, 40, 25, 99, 51, 100]
newlst = sorted(lst)  # 升序print(newlst)  # [25, 37, 40, 40, 47, 48, 51, 73, 99, 99, 100]print(list(enumerate(newlst)))  # [(0, 25), (1, 37), (2, 40), (3, 40), (4, 47), (5, 48), (6, 51), (7, 73), (8, 99), (9, 99), (10, 100)]print(20, bisect.bisect(newlst, 20))  # 0
print(30, bisect.bisect(newlst, 30))  # 1
print(40, bisect.bisect(newlst, 40))  # 4
print(20, bisect.bisect_left(newlst, 20))  # 0
print(30, bisect.bisect_left(newlst, 30))  # 1
print(50, bisect.bisect_left(newlst, 40))  # 2for x in (20, 30, 40, 100):bisect.insort_left(newlst, x)print(newlst)  # [20, 25, 30, 37, 40, 40, 40, 47, 48, 51, 73, 99, 99, 100, 100]
应用

在这里插入图片描述

import bisectdef get_grade(score):breakpoints = [160, 70, 80, 90]grades = 'EDCBA'return grades[bisect.bisect(breakpoints, score)]for x in (91, 82, 77, 65, 50, 60, 70):print('{} => {}'.format(x, get_grade(x)))
输出:
91 => A
82 => B
77 => C
65 => E
50 => E
60 => E
70 => C
将前面的链表,封装成容器

1、提供__getitem__、__iter__、__setitem__方法
2、使用一个列表,辅助完成上面的方法
3、进阶:不使用列表,完成上面的方法
进阶题
实现类property装饰器,类名称为Property。
基本结构如下,是一个数据描述器

class Property:  # 数据描述器def _init_(self):passdef _get_(self, instance, owner):passdef _set_(self, instance, value):passclass A:def __init__(self, data):self._data = data@Property
def data(self):return self._data@data.setter
def data(self, value):self._data = value

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/914216.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/914216.shtml
英文地址,请注明出处:http://en.pswp.cn/news/914216.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis的一些疑问

spring集成redisCacheEvict(value "commonCache", key "#uniqueid_userInfo")什么时候会执行缓存移除呢&#xff1f;如果方法执行异常是否移除&#xff1f;如果缓存不存在还会移除么&#xff1f;这个移除会在redis的执行历史命令中监控到么&#xff1f;.…

3.检查函数 if (!CheckStart()) return 的妙用 C#例子

在桌面/WPF 开发中&#xff0c;我们经常需要在按钮事件里先判断“能不能做”&#xff0c;再决定“怎么做”。如果校验不过&#xff0c;就直接返回&#xff1b;校验通过&#xff0c;才继续执行业务逻辑。 今天分享一个极简写法&#xff1a;if (!CheckStart()) return;&#xff0…

炎热工厂救援:算法打造安全壁垒

高温天气下智慧工厂&#xff1a;算法赋能&#xff0c;安全救援无忧背景&#xff1a;极端高温下工厂的严峻挑战近年来&#xff0c;极端高温天气频发&#xff0c;部分地区气温接近甚至超过50℃。在这样酷热的环境中&#xff0c;工厂面临着诸多严峻问题。一方面&#xff0c;高温容…

pgsql模板是什么?

查找所有的数据库 select datname from pg_database运行该命令后&#xff0c;我们会发现其中出现了一些其它的数据库接下来&#xff0c;我们分析 template0 和 template1 的作用。template1 template1 是 PostgreSQL 默认用于创建新数据库的模板。当执行 CREATE DATABASE new_d…

LLM 不知道答案,但是知道去调用工具获取答案?

思考&#xff1a; LLM 自己“不知道”某个事实性问题的答案&#xff0c;但仍然能“知道”去调用工具获取正确答案&#xff0c;这听起来确实有点像个悖论该内容触及了大型语言模型&#xff08;LLM&#xff09;的核心局限性以及&#xff08;Agents&#xff09;的智能所在。实际上…

2025年7月11日学习笔记一周归纳——模式识别与机器学习

2025年7月11日学习笔记&一周归纳——模式识别与机器学习一.一周工作二.我的一些笔记汇总三.发现的一些新的学习资料和爱用好物1.百度网盘AI笔记&#xff1a;2.b站资料&#xff1a;3.听说的一些好书&#xff1a;一.一周工作 本周学习了清华大学张学工汪小我老师的模式识别与…

LeetCode 138题解 | 随机链表的复制

随机链表的复制一、题目链接二、题目三、分析四、代码一、题目链接 138.随机链表的复制 二、题目 三、分析 数据结构初阶阶段&#xff0c;为了控制随机指针&#xff0c;我们将拷贝结点链接在原节点的后面解决&#xff0c;后面拷贝节点还得解下来链接&#xff0c;非常麻烦。这…

【计算机存储架构】分布式存储架构

引言&#xff1a;数据洪流时代的存储革命“数据是新时代的石油” —— 但传统存储正成为制约数据价值释放的瓶颈核心矛盾&#xff1a;全球数据量爆炸增长&#xff1a;IDC预测2025年全球数据量将达175ZB&#xff08;1ZB10亿TB&#xff09;传统存储瓶颈&#xff1a;单机IOPS上限仅…

【Linux-云原生-笔记】数据库操作基础

一、什么是数据库&#xff1f;数据库就是一个有组织、可高效访问、管理和更新的电子化信息&#xff08;数据&#xff09;集合库。简单来说&#xff0c;数据库就是一个高级的Excel二、安装数据库并初始化1、安装数据库&#xff08;MySQL&#xff09;dnf search一下mysql数据库的…

HarmonyOS中各种动画的使用介绍

鸿蒙&#xff08;HarmonyOS&#xff09;提供了丰富的动画能力&#xff0c;涵盖属性动画、显式动画、转场动画、帧动画等多种类型&#xff0c;适用于不同场景的交互需求。以下是鸿蒙中各类动画的详细解析及使用示例&#xff1a;1. 属性动画&#xff08;Property Animation&#…

CSP-S 模拟赛 10

T1 洛谷 U490727 返乡 思路 首先要意识到一个问题&#xff0c;就是如果所有人总分一定&#xff0c;那么是不会出现偏序的。 可以感性理解一下&#xff0c;就是对于 i,ji, ji,j&#xff0c; 若 ai≤aj,bi≤bja_i \leq a_j, b_i \leq b_jai​≤aj​,bi​≤bj​&#xff0c;那么…

CMD,PowerShell、Linux/MAC设置环境变量

以下是 CMD&#xff08;Windows&#xff09;、PowerShell&#xff08;Windows&#xff09;、Linux/Mac 在 临时/永久 环境变量操作上的对比表格&#xff1a;环境变量操作对照表&#xff08;CMD vs PowerShell vs Linux/Mac&#xff09;操作CMD&#xff08;Windows&#xff09;P…

MySQL(131)如何解决MySQL CPU使用率过高问题?

解决MySQL CPU使用率过高的问题需要从多个方面进行排查和优化&#xff0c;包括查询优化、索引优化、配置优化和硬件资源的合理使用等。以下是详细的解决方案和相应的代码示例。 一、查询优化 1. 检查慢查询 使用MySQL的慢查询日志来找到执行时间长的查询。 SET GLOBAL slow_que…

docker基础与常用命令

目录 一.docker概述 1.docker与虚拟机区别 2.Linux 六大命名空间 3.Docker 的核心技术及概念 二.docker部署安装 三.docker常用命令 1.搜索镜像 2.获取镜像 3.查看镜像信息 4.添加镜像标签 5.删除镜像 6.存出与载入镜像 7.上传镜像 8.创建容器 9.查看容器状态 1…

Cypress与多语言后端集成指南

Cypress 简介 基于 JavaScript 的前端测试工具,可以对浏览器中运行的任何内容进行快速、简单、可靠的测试Cypress 是自集成的,提供了一套完整的端到端测试,无须借助其他外部工具,安装后即可快速地创建、编写、运行测试用例,且对每一步操作都支持回看不同于其他只能测试 UI…

计算机毕业设计ssm基于JavaScript的餐厅点餐系统 SSM+Vue智慧餐厅在线点餐管理平台 JavaWeb前后端分离式餐饮点餐与桌台调度系统

计算机毕业设计ssm基于JavaScript的餐厅点餐系统0xig8788&#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。扫码点单、手机支付、后厨实时出票已经成为食客对餐厅的基本预期。传统的…

wedo稻草人-----第32节(免费分享图纸)

夸克网盘&#xff1a;https://pan.quark.cn/s/ce4943156861 高清图纸源文件&#xff0c;需要的请自取

Jmeter函数的使用

函数名作用用法${__Random(,,)}${__RandomString(,,)}随机生成一些东西${__Random(000,999,)} ${__Random(${test1},${test2},)}${__RandomString(${__Random(3,9,)},asdfghjkl,)}${__time(,)}获取当前的时间戳&#xff0c;也可以定义格式${__CSVRead(,)}读取CSV文件的格式&…

Windows 用户账户控制(UAC)绕过漏洞

漏洞原理CVE-2021-31199 是一个 Windows 用户账户控制&#xff08;UAC&#xff09;绕过漏洞&#xff0c;CVSS 3.1 评分 7.8&#xff08;高危&#xff09;。其核心原理如下&#xff1a;UAC 机制缺陷&#xff1a;Windows UAC 通过限制应用程序权限提升系统安全性&#xff0c;但某…

comfyUI-controlNet-线稿软边缘

{WebUI&comfyUI}∈Stable Diffuision&#xff0c;所以两者关于ContrlNet的使用方法的核心思路不会变&#xff0c;变的只是comfyUI能够让用户更直观地看到&#xff0c;并且控制生图的局部过程。 之前的webUI中涉及到ContrlNet部分知识&#xff1a;SD-细节控制-CSDN博客 概…