Python-魔术方法-创建、初始化与销毁-hash-bool-可视化-运算符重载-容器和大小-可调用对象-上下文管理-反射-描述器-二分-学习笔记

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

魔术方法

特殊属性

查看属性

如果dir(lobji)参数obj包含方法 dir(),该方法将被调用。如果参数obj不包含__dir__(),

该方法将最大限度地收集参数信息。

dir()对于不同类型的对象具有不同的行为:

如果对象是模块对象,返回的列表包含模块的属性名。

如果对象是类型或者类对象,返回的列表包含类的属性名,及它的基类的属性名。

否则,返回列表包含对象的属性名,它的类的属性名和类的基类的属性名。

animal. py
python 复制代码
class Animal:
    x = 123
    def __init__(self, name):
        self._name = name
        self._age = 10
        self.weight = 20
test.py
python 复制代码
print('animal Modulel\'s names ={}'.format(dir()))  # 模块的属性

# cat.py
import animal

from animal import Animal


class 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、创建、初始化与销毁
    initdel
    2、 hash
    3、 bool
    4、 可视化
    5、 运算符重载
    6、 容器和大小
    7、 可调用对象
    8、上下文管理
    9、 反射
    10、 描述器
    11、 其他杂项

hash

python 复制代码
class A:
    def __init__(self, name, age=18):
        self.name = name

    def __hash__(self):
        return 1

    def __repr__(self):
        return self.name


print(hash(A('tom')))               # 1

print((A('tom'), A('tom'))) #(tom, tom)

print([A('tom'), A('tom')])     # [tom, tom]

print('~'*30)


s = {A('tom'), A('tom')}  # set

print(s)  # 去重了吗        {tom, tom}
    
print({tuple('t'), tuple('t')})     # {('t',)}

print({('tom',), ('tom',)})     # {('tom',)}
    
print({'tom', 'tom'})       # {'tom'}

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

python 复制代码
class A:
    def __init__(self, name, age=18):
        self.name = name

    def __hash__(self):
        return 1

    def __repr__(self):
        return self.name

    def __eq__(self, other):  # 这个函数作用?
        return self.name == other.name


print(hash(A('tom')))  # 1

print((A('tom'), A('tom')))  # (tom, tom)

print([A('tom'), A('tom')])  # [tom, tom]

print('~' * 30)

s = {A('tom'), A('tom')}  # set

print(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。

练习参考

python 复制代码
from collections import Hashable


class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __hash__(self):
        return hash((self.x, self.y))

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y




p1 = 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

python 复制代码
class A: pass


print(bool(A()))

if A():
    print('Real A')


class B:

    def __bool__(self):
        return False


print(bool(B))
print(bool(B()))

if B:
    print('Real B')


class C:
    def _len_(self):
        return 0


print(bool(C()))

if C():
    print('Real C')
输出:
True
Real A
True
False
Real B
True
Real C

可视化

python 复制代码
class A:
    def __init__(self, name, age=18):
        self.name = name
        self.age = age

    def __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 json
        return 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模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作

python 复制代码
class A:
    def __init__(self, name, age=18):
        self.name = name
        self.age = age

    def __sub__(self, other):
        return self.age - other.age

    def __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 n

print(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)

python 复制代码
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __eq__(self, other):
        return self.x == other.x and self.y == other.y

    def __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 装饰器

python 复制代码
from functools import total_ordering


@total_ordering
class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

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

    def __gt__(self, other):
        return self.age > other.age


tom = Person('tom', 20)
jerry = Person('jerry', 16)

print(tom > jerry)  # True

print(tom < jerry)  # False

print(tom >= jerry)  #True
print(tom <= jerry) # False

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

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

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

    def __gt__(self, other):
        return self.age > other.age

    def __ge__(self, other):
        return self.age >= other.age


tom = 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

容器相关方法

python 复制代码
class A(dict):
    def __missing__(self, key):
        print('Missing key : ', key)

        return 0


a = A()
print(a['k'])

输出:
Missing key :  k
0

思考

为什么空字典、空字符串、空元组、空集合、空列表等可以等效为False?

练习

将购物车类改造成方便操作的容器类

python 复制代码
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] = value

    def __str__(self):
        return str(self.items)

    def __add__(self, other):  # +
        self.items.append(other)
        return self


cart = 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中一切皆对象,函数也不例外。

python 复制代码
def foo():
    print(foo.__module__, foo.__name__)


foo()
# 等价于
foo.__call__()
输出:
__main__ foo
__main__ foo

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

可调用对象

可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。

python 复制代码
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __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 = 0
        for x in args:
            ret += x
        self.ret = ret
        return ret


adder = Adder()
print(adder(4, 5, 6))   # 15
print(adder.ret)    #15

练习:

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

python 复制代码
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

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

python 复制代码
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语法。

python 复制代码
with open('test') as f:
    pass

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

python 复制代码
class Point:
    pass


with Point() as p:  # AttributeError: _exit_
    pass

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

上下文管理对象

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

python 复制代码
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可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。

上下文管理的安全性

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

python 复制代码
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运行环境直接退出了。

python 复制代码
import sys


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:
    sys.exit(-100)
    print('do sth.')
    
print('outer')
输出:
init
enter
exit

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

with语句

python 复制代码
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。修改上例

python 复制代码
class Point:
    def __init__(self):
        print('init')

    def __enter__(self):
        print('enter')

        return self

    def __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的值,则压制异常;否则,继续抛出异常

python 复制代码
class Point:
    def __init__(self):
        print('init')

    def __enter__(self):
        print('enter')

        return self

    def __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、使用上下文管理方法来显示该函数的执行时长

python 复制代码
import time


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

装饰器实现

python 复制代码
import datetime
from functools import wraps
import time


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

    return wrapper


@timeit
def add(x, y):
    time.sleep(2)
    return x + y


print(add(4, 5))        
输出:
add took 2.000806s
9

用上下文实现

python 复制代码
import datetime
from functools import wraps
import time


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


class Timeit:
    def __init__(self, fn):
        self.fn = fn

    def __enter__(self):
        self.start = datetime.datetime.now()
        return self.fn

    def __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))

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

python 复制代码
import datetime
from functools import wraps
import time


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


class Timeit:
    def __init__(self, fn):
        self.fn = fn

    def __enter__(self):
        self.start = datetime.datetime.now()
        return self.fn

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

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

python 复制代码
import datetime
from functools import wraps
import time


class Timeit:
    def __init__(self, fn):
        self.fn = fn

    def __enter__(self):
        self.start = datetime.datetime.now()
        return self.fn

    def __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))
        pass

    def __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 + y


add(1, 2)

print(add.__doc__)
输出:
__call__ add took 2.00509s. call
None

思考

如何解决文档字符串问题?

方法一

doc 直接修改

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

方法二

使用functools.wraps函数

python 复制代码
import datetime
from functools import wraps
import time


class 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.fn

    def __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))
        pass

    def __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 + y


add(1, 2)

print(add.__doc__)

print(Timeit(add).__doc__)

输出:
__call__ add took 2.004217s. call
This is add function.
This is add function.

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

上下文应用场景

1.增强功能

在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。

  1. 资源管理

打开了资源需要关闭,例如文件对象、网络连接、数据库连接等

  1. 权限验证

在执行代码之前,做权限的验证,在__enter__中处理

contextlib.contextmanager

它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现 __enter__和

__exit__方法。

对下面的函数有要求,必须有yield,也就是这个函数必须返回一个生成器,且只有yield一个值。

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

python 复制代码
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 。

python 复制代码
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__ 方法方便。

python 复制代码
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装饰器方式,如果业务复杂,用类的

方式加 enterexit 方法方便。

反射

概述

运行时,区别于编译时,指的是程序被加载到内存中执行的时候。

反射,reflection,指的是运行时获取类型定义信息。

一个对象能够在运行时,像照镜了一样,反射出其类型信息。

简单说,在Python中,能够通过一个对象,找出其type、class. attribute或method的能力,称为反射或者自省。

具有反射能力的函数有:type()、isinstance()、callable()、dir()、getattr()

反射相关的函数和方法

需求

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

python 复制代码
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __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提供了内置的函数。

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

python 复制代码
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __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都是定义时就决定了,因此反射能力具有更大的灵活性。

练习

命令分发器,通过名称找对应的函数执行。

思路:名称找对象的方法

python 复制代码
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':
                break

            getattr(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()
python 复制代码
class Base:
    n = 0


class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def 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()
python 复制代码
class Base:
    n = 0


class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def 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)  # missing

p1.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__中,就需要自己完成。

python 复制代码
class Base:
    n = 0


class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def 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()
python 复制代码
class Point:
    z = 5

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __delattr__(self, item):
        print('Can not del {}'.format(item))


p = Point(14, 5)

del p.x     # Can not del x

p.z = 15

del p.z     # Can not del z

del p.z     # Can not del z
    
print(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
python 复制代码
class Base:
    n = 0


class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __getattr__(self, item):
        return "missing {}".format(item)

    def __getattribute__(self, item):
        return item


p1 = 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__方法,因沩表示属性没有找到。

python 复制代码
class Base:
    n = 0


class Point(Base):
    z = 6

    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __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 是属性的所属的类

请思考下面程序的执行流程是什么?

python 复制代码
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 方法,看看变化

python 复制代码
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说明

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

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

python 复制代码
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的实例属性也可以?

python 复制代码
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中,一个类实现了 getset,delete 三个方法中的任何一个方法,就是描述器。

如果仅实现了__get__,就是非数据描述符 non-data descriptor ;

同时实现了__get__、set 就是数据描述符 data descriptor。

如果一个类的类属性设置为描述器,那么它被称为owner属主。

属性的访问顺序

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

python 复制代码
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' # 增加实例属性x


print(' - ' * 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 方法。

python 复制代码
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 = value


class B:
    x = A()

    def __init__(self):
        print('B.init')
        self.x = 'b.x' # 增加实例属性x


print(' - ' * 20)

print(1,B.x)    # 1 <__main__.A object at 0x1006f5550>

print(2,B.x.a1) # a1

print('=' * 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__方法,看看变化。

python 复制代码
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 = value

class B:
    x = A()

    def __init__(self):
        print('B.init')
        self.x = 'b.x'  # 增加实例属性x
        self.y = 'b.y'


print(' - ' * 20)

print(1, B.x)  # 1 <__main__.A object at 0x1006f5550>

print(2, B.x.a1)  # a1

print('=' * 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 = value


A.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(函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。

python 复制代码
class A:
    @classmethod
    def foo(cls):  # 非数据描述器
        pass

    @staticmethod  # 非数据描述器
    def bar():
        pass

    @property  # 数据描述器
    def z(self):
        return 5

    def getfoo(self):  # 非数据描述器
        return self.foo

    def __init__(self):  # 非数据描述器
        self.foo = 100
        self.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装饰器的功能
python 复制代码
# 类staticmethod装饰器
class StaticMethod:  # 怕冲突改名
    def __init__(self, fn):
        self._fn = fn

    def __get__(self, instance, owner):
        return self._fn


class A:
    @StaticMethod
    # stmtd = StaticMethod(stmtd)
    def stmtd():
        print('static method')


A.stmtd()
A().stmtd()
输出:
static method
static method
  1. 实现ClassMethod装饰器,完成classmethod装饰器的功能
python 复制代码
from functools import partial


#  类classmethod装饰器
class ClassMethod:  # 怕冲突改名
    def __init__(self, fn):
        self._fn = fn

    def __get__(self, instance, owner):
        ret = self._fn(owner)
        return ret


class 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函数

python 复制代码
from functools import partial


#  类classmethod装饰器
class ClassMethod:  # 怕冲突改名
    def __init__(self, fn):
        self._fn = fn

    def __get__(self, instance, cls):
        ret = partial(self._fn, cls)
        return ret


class 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. 对实例的数据进行校验
python 复制代码
class Person:
    def __init__(self, name:str, age:int):
        self.name = name
        self.age = age

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

思路

  1. 写函数,在 init 中先检查,如果不合格,直接抛异常
  2. 装饰器,使用inspect模块完成
  3. 描述器
python 复制代码
# 写函数检查
class Person:
    def __init__(self, name: str, age: int):
        params = ((name, str), (age, int))
        if not self.checkdata(params):
            raise TypeError
        self.name = name
        self.age = age


    def checkdata(self, params):
        for p, t in params:
            if not isinstance(p, t):
                return False

        return True


p = 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

这种方法耦合度太高。

装饰器的方式,前面写过类似的,这里不再赘述。

描述器方式

需要使用数据描述器,写入实例属性的时候做检查

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

    def __get__(self, instance, owner):
        if instance is not None:
            return instance.__dict__[self.name]
        return self

    def __set__(self, instance, value):
        if not isinstance(value, self.type):
            raise TypeError(value)
        instance.__dict__[self.name] = value


class Person:
    name = Typed('name', str)  # 不优雅
    age = Typed('age', int)  # 不优雅

    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age


p = 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模块

先做个实验

python 复制代码
params = inspect.signature(Person).parameters

OrderedDict({'name': <Parameter "name: str">, 'age': <Parameter "age: int">})

看看返回什么结果

完整代码如下

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

    def __get__(self, instance, owner):
        if instance is not None:
            return instance.__dict__[self.name]
        return self

    def __set__(self, instance, value):
        if not isinstance(value, self.type):
            raise TypeError(value)

        instance.__dict__[self.name] = value


import inspect


def typeassert(cls):
    params = inspect.signature(cls).parameters
    print(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 = name
        self.age = age

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

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

python 复制代码
class Typed:
    def __init__(self, type):
        self.type = type

    def __get__(self, instance, owner):
        pass

    def __set__(self, instance, value):
        print('T.set', self, instance, value)
        if not isinstance(value, self.type):
            raise ValueError(value)


import inspect


class TypeAssert:
    def __init__(self, cls):
        self.cls = cls  # 记录着被包装的Person类新算业
        params = inspect.signature(self.cls).parameters
        print(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 = name
        self.age = age


p1 = 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,首先在大致的中点元素开始和待插入数比较,如果大则和右边的区域的中点继续

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

python 复制代码
def insert_sort(orderlist, i):
    ret = orderlist[:]
    low = 0
    high = len(orderlist) - 1
    while low < high:
        mid = (low + high) // 2
        if 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插入的位置不对,为什么?

python 复制代码
def insert_sort(orderlist, i):
    ret = orderlist[:]
    low = 0
    high = len(orderlist)		# 去掉-1 
    while low < high:
        mid = (low + high) // 2
        if 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系,用于实际插入。
    默认重复时从右边插入。
python 复制代码
import bisect

lst = [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))  # 2

for 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]
应用
python 复制代码
import bisect


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

基本结构如下,是一个数据描述器

python 复制代码
class Property:  # 数据描述器
    def _init_(self):
        pass

    def _get_(self, instance, owner):
        pass

    def _set_(self, instance, value):
        pass


class A:
    def __init__(self, data):
        self._data = data


@Property
def data(self):
    return self._data


@data.setter
def data(self, value):
    self._data = value
相关推荐
Y1nhl24 分钟前
力扣_二叉树的BFS_python版本
python·算法·leetcode·职场和发展·宽度优先
Q_Q5110082851 小时前
python的婚纱影楼管理系统
开发语言·spring boot·python·django·flask·node.js·php
若兰幽竹1 小时前
【从零开始编写数据库:基于Python语言实现数据库ToyDB的ACID特性】
数据库·python
xiaocainiao8812 小时前
Python 实战:构建 Git 自动化助手
git·python·自动化
nightunderblackcat2 小时前
新手向:使用Python将多种图像格式统一转换为JPG
开发语言·python
engchina2 小时前
Python PDF处理库深度对比:PyMuPDF、pypdfium2、pdfplumber、pdfminer的关系与区别
开发语言·python·pdf
Lo-Y-eH2 小时前
Openpyxl:Python操作Excel的利器
python·excel
DAWN_T172 小时前
Transforms
pytorch·python·机器学习·jupyter·pycharm
一百天成为python专家3 小时前
python库之jieba 库
开发语言·人工智能·python·深度学习·机器学习·pycharm·python3.11
伊成3 小时前
docker安装Consul笔记
笔记·docker·consul