pyhton中的魔术方法
特殊属性
属性 | 说明 |
---|---|
__name__ | 类、函数、方法等的名字 |
__module__ | 类定义所在的模块名 |
__class__ | 对象或类所属的类 |
__bases__ | 类的基类的元组,顺序为它们在基类列表中出现的顺序 |
__doc__ | 类、函数的文档字符串,如果没有定义则为 None |
__mro__ | 类的mro,class.mro()返回的结果的保存在 __mro__ 中 |
__dict__ | 类或实例的属性,可写的字典 |
查看属性
__dir__
方法
方法 | 说明 |
---|---|
__dir__ |
返回类或者对象的所有成员 名称列表 。通过内建函数 dir() 操作实例就是调用 __dir__() 。 |
__dir__
方法一定要返回一个可迭代对象(只要是可迭代对象就可以)- 不管
__dir__
方法返回的是什么要的可迭代对象,最终都会被解释器转换为一个列表
class A:
def __dir__(self):
return 1
a = A()
print(dir(a))
-------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
Traceback (most recent call last):
File "/data/test1/test3.py", line 6, in <module>
print(dir(A()))
TypeError: 'int' object is not iterable
class A:
def __dir__(self):
return {1,2}
a = A()
print(dir(a))
----------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
[1, 2]
class A:
def __dir__(self):
yield from {"a":1,"b":2}
a = A()
print(dir(a))
------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
['a', 'b']
dir([obj]) 的使用要点
- 如果
dir([obj])
参数obj
中包含方法__dir__()
,该方法将被调用。 - 如果参数
obj
不包含__dir__()
,该方法将最大限度地收集属性信息。
dir(obj)对于不同类型的对象obj具有不同的行为
dir(obj) 指定对象调用
- 如果对象是模块对象,返回的列表包含模块的属性名和变量名
- 如果对象是类型或者说是类对象,返回的列表包含类的属性名,及它的祖先类的属性名
- 如果是类的实例
- 有
__dir__
方法,返回可迭代对象的返回值 - 没有
__dir__
方法,则尽可能收集实例的属性名、类的属性和祖先类的属性名
- 有
dir() 不指定对象调用
- 如果
obj
不写,返回列表会根据调用dir()函数的环境不同而包含内容不同- 在模块中调用dir(),返回模块的属性和变量名
- 在函数中调用dir(),返回本地作用域的变量名
- 在方法中调用dir(),返回本地作用域的变量名
animal.py
class Animal:
x = 123
def __init__(self, name):
self._name = name
self.__age = 10
self.weight = 20
print('animal Module\'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('---------')
print('Current Module\'s names = {}'.format(dir())) # 模块名词空间内的属性
print('animal Module\'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))) # 类Cat的dir()
print('~~~~~~~~~~')
tom = Cat('tom')
print(sorted(dir(tom))) # 实例tom的属性、Cat类及所有祖先类的类属性
print(sorted(tom.__dir__())) # 同上
# 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__)
----------------执行结果----------------
dir()的测试如下
class Person:
def show(self): # 方法中
a = 100
t = int(a)
print(dir())
def test(a=50, b=100): # 函数中
print(dir())
Person().show()
test()
------------------ 显示结果如下 ------------------
['a', 'self', 't']
['a', 'b']
locals 和 globals 返回当前环境的属性字典
- locals 返回当前环境的属性字典(locals()函数执行的换将不同,返回的结果不同,都是当权作用域中的属性字典)
- locals 返回的局部作用域中的属性字典中不包括实例的属性
- globals 返回全局黄静的属性字典(不管是否在全局环境中执行还是在局部作用域中执行,返回的都是全局的属性字典)
class A:
def __dir__(self):
yield from [1,2]
def getlocal1(self):
m = 333
self.x = 111
self.y = 222
return locals()
@classmethod
def getlocal2(cls):
return locals()
a = A()
print(1, dir(a))
print(2,locals())
print(3,globals())
print(4,a.getlocal1())
print(5,a.getlocal2())
------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 [1, 2]
2 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7feaa27f9c18>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/data/test1/test3.py', '__cached__': None, 'A': <class '__main__.A'>, 'a': <__main__.A object at 0x7feaa277b5f8>}
3 {'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7feaa27f9c18>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/data/test1/test3.py', '__cached__': None, 'A': <class '__main__.A'>, 'a': <__main__.A object at 0x7feaa277b5f8>}
4 {'m': 333, 'self': <__main__.A object at 0x7feaa277b5f8>}
5 {'cls': <class '__main__.A'>}
魔术方法的分类
- 创建、初始化与销毁
__new__
、__init__
、__del__
- hash
- bool
- 可视化
- 运算符重载
- 容器和大小
- 可调用对象
- 上下文管理
- 反射
- 描述器
- 其他杂项
除了
__new__
之外,所有的魔术方法只和实例相关,也就是说只有实例可以调用
object 中的特殊方法
l = dir(object)
print(type(l))
print('----------------------')
for i in l:
print(i)
----------------------------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
<class 'list'>
----------------------
__class__
__delattr__
__dir__
__doc__
__eq__
__format__
__ge__
__getattribute__
__gt__
__hash__
__init__
__init_subclass__
__le__
__lt__
__ne__
__new__
__reduce__
__reduce_ex__
__repr__
__setattr__
__sizeof__
__str__
__subclasshook__
实例化相关的魔术方法
__new__
and __init__
实例化和初始化函数
方法 | 意义 |
---|---|
__new__ |
实例化一个对象 该方法需要返回一个值,如果该值不是cls的实例,则不会调用 __init__ ,也就是说 __init__ 只有在实例化的时候才会调用 该方法 __new__ 永远都是静态方法 |
__init__ |
初始化一个实例对象,为实例对象添加最初的属性 |
class A:
def __new__(cls, *args, **kwargs):
print(cls)
print(*args)
print(**kwargs)
return super().__new__(cls)
# return 1
# return None
def __init__(self, name):
self.name = name
a = A('scm')
print(a)
------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test4.py
<class '__main__.A'>
scm
<__main__.A object at 0x7fd6b70e7668>
-
__new__
方法很少使用,即使创建了该方法,也会使用return super().__new__(cls)
基类object的__new__
方法来创建实例并返回。 -
实际上在内存构建一个对象不是我们能够实现的,我们就将它交给解释器来做,因此我们就使用
return super().__new__(cls)
基类object的__new__
方法来创建。 -
由于除了 Object 有
__new__
其他的类都没有__new__
,所以实际上我们使用super().__new__(cls)
就是在调用object.__new__(cls)
-
上面的过程是:
A('scm') ==> 解释器将 'scm' 传递给 本地的
__new__
,'scm'由*args
来接收,但是我们这里的__new__
并没有用到'scm' ==> 调用super().__new__(cls)
==>object.__new__(cls)
构建出一个示例对象 ==在即将返回的瞬间=> 由解释器再调用本地的__init__(self, name)
将'scm' 传递给__init__
进行初始化 ==> 解释器将初始化好的实例对象返回
__del__
销毁(析构)函数
-
类中可以定义
__del__
方法,称为date函数(方法)
。不要手动调用,交给解释器调用。- 作用:销毁类的实例的时候解释器自动调用,以释放占用的资源。其中就放些清理资源的代码,比如释放连接。
- 注意这个方法 不是用来清理对象的,不能引起对象的真正销毁,只是用来在解释器销毁引用计数为 0 的对象的时候,由解释器自动调用它,来执行一些必要的工作,比如说释放对象占用的网络、内存资源等。
- 可以类比与 AWK 中的 end 的功能
__del__
方法 可以手工调用,不管手工是否调用多少次,在对象消亡的时候后,解释器还会调用一次。
-
使用
del
语句删除实例,引用计数减1
。当引用计数为0时,会自动调用__del__
方法。 -
由于Python实现了垃圾回收机制,不能确定对象何时执行垃圾回收。
测试1
import time
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
def __del__(self):
print('----del-----')
tom = Person('Tom',100) # 对象应用计数为1
print('''will del tom''')
time.sleep(2)
del tom # 对象应用计数减去1 变成 0,解释器就会垃圾回收改对象,从而调用 __del__ 函数。
time.sleep(2)
print('''tom deleted''')
time.sleep(2)
print('====end====')
--------------------------------------
C:\python36\python.exe D:/pyporject/test/test3.py
will del tom
----del-----
tom deleted
====end====
Process finished with exit code 0
测试2
import time
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
def __del__(self):
print('----del-----')
tom = Person('Tom',100)
print('''will del tom''')
time.sleep(2)
# del tom # 注释这一行
time.sleep(2)
print('''tom deleted''')
time.sleep(2)
print('====end====')
--------------------------------------
C:\python36\python.exe D:/pyporject/test/test3.py
will del tom
tom deleted
====end====
----del----- # 解释器结束,所有对象都要消亡,tom执行的对象也不例外,所以解释器要调用 __del__
Process finished with exit code 0
测试3
import time
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
def __del__(self):
print('----del-----')
tom = Person('Tom',100)
print('''will del tom''')
time.sleep(2)
scm = tom # 引用计数加1,tom对应的对象的引用计数变成2,由标识符scm、tom引用
del tom # 引用计数减1,tom对应的对象的引用计数变成1,由标识符scm引用
time.sleep(2)
print('''tom deleted''')
time.sleep(2)
print('====end====')
------------------------------------------
C:\python36\python.exe D:/pyporject/test/test3.py
will del tom
tom deleted
====end====
----del-----
Process finished with exit code 0
测试4
import time
class Person:
def __init__(self, name, age=18):
self.name = name
self.__age = age
def __del__(self):
print('delete {}'.format(self.name))
def test():
tom = Person('tom')
tom.__del__() # 手动调用
tom.__del__()
tom.__del__()
tom.__del__()
print('======start======')
tom2 = tom
tom3 = tom2
print(1, 'del')
del tom
time.sleep(3)
print(2, 'del')
del tom2
time.sleep(3)
print('~~~~~~~~~')
del tom3 # 注释一下看看效果
time.sleep(3)
print('=======end')
test()
由于垃圾回收对象销毁时,才会真正清理对象,还会在回收对象之前自动调用 __del__
方法,除非你明确知道自己的目的,建议不要手动调用这个方法。
hash 相关的魔术方法
__hash__
方法
方法 | 说明 |
---|---|
__hash__ |
内建函数 hash() 调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash。 |
- 如果一个类中没有定义
__hash__
函数,就会向上继承父类的__hash__
函数 - 如果父类中以及父类的祖先类中都没有定义
__hash__
函数,就会找到object类中的__hash__
函数 - 如果在当前类中定义了
__hash__
函数,该类的实例就都是可hash的 - 如果在该类中
__hash__ = None
那么改类的多有实例都是不可hash的, 但是该类本身不一定是不可hash的,原因同上
可hash
from collections import Hashable
class A:
pass
a = A()
print(isinstance(a,Hashable))
print(hash(a))
------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
True
-9223363251172910020
不可hash
from collections import Hashable
class A:
pass
__hash__ = None
a = A()
print(isinstance(a,Hashable))
print(hash(a))
------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
False
Traceback (most recent call last):
File "/data/test1/test3.py", line 10, in <module>
print(hash(a))
TypeError: unhashable type: 'A'
print(hash(1))
print(hash('tom'))
print(hash(('tom',)))
class A:
def __init__(self, name, age=23):
self.name = name
def __hash__(self):
return 1
def __repr__(self):
return self.name
print(hash(A('scm')))
print((A('scm'), A('scm')))
print([A('scm'), A('scm')])
print('=============================')
print({1, 1})
s = {A('scm'), A('scm')} # set
print(s) # 去重了吗
print({tuple('t'), tuple('t')})
print({('scm',), ('scm',)})
print({'scm', 'scm'})
--------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1
(scm, scm)
[scm, scm]
=============================
{1}
{scm, scm}
{('t',)}
{('scm',)}
{'scm'}
上例中set为什么不能剔除相同的key?
hash值相同就会去重吗?
class A:
def __init__(self, name, age=18):
self.name = name
def __hash__(self):
return 1
def __eq__(self, other): # 这个函数作用是判断两者的内容是否一致
return self.name == other.name
def __repr__(self):
return self.name
print(hash(A('scm')))
print((A('scm'), A('scm')))
print([A('scm'), A('scm')])
print('=============================')
s = {A('scm'), A('scm')} # set
print(s)
print({tuple('t'), tuple('t')})
print({('scm',), ('scm',)})
print({'scm', 'scm'})
-----------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test4.py
1
(scm, scm)
[scm, scm]
=============================
{scm}
{('t',)}
{('scm',)}
{'scm'}
__eq__
方法
方法 | 说明 |
---|---|
__eq__ |
对应 == 操作符,判断2个对象是否相等,返回bool值 |
__hash__
方法只是返回一个hash值作为set的key,但是去重,还需要__eq__
来判断2个对象是否相等。- hash值相等,只是hash冲突,不能说明两个对象是相等的。
- 因此,一般来说提供
__hash__
方法是为了作为set或者dict的key,所以去重要同时提供__eq__
方法来判断两者是否相同。 - 不可hash对象isinstance(p1, collections.Hashable)一定为False。
- 去重需要提供
__eq__
方法。
思考:
list类实例为什么不可hash?
class list(object):
"""
list() -> new empty list
list(iterable) -> new list initialized from iterable's items
"""
......其他方法省略......
__hash__ = None # __hash__ 函数被置为None所以不可hash
源码中有一句 __hash__ = None
,也就是如果调用 __hash__()
相当于None(),一定报错。
所有类都继承 object
,而object
这个类是具有 __hash__()
方法的,如果一个类不能被hash,就把 __hash__
设置为 None
。
通过点类
示例演示 可hash 和 去重
设计二维坐标类Point,使其成为可hash类型,并比较2个坐标的实例是否相等?
from collections import Hashable
class Point:
def __init__(self,x,y):
self.x = x
self.y = y
def __repr__(self):
return "< Point ({},{}) id: {} >".format(self.x , self.y, id(self))
def __hash__(self):
return hash((self.x, self.y))
def __eq__(self, other):
return self.x == other.x and self.y == other.y
a = Point(1,2)
b = Point(1,2)
print(1,a)
print(2,b)
s = {a,b}
print(3,s)
print(4,hash(a))
print(5,hash(b))
print(6, a is b)
print(7, a == b) # True, 因为 == 运算符号使用的是 __eq__ 方法来判断的
print(8, hex(id(a)), hex(id(a)))
print(9,isinstance(a, Hashable))
--------------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test4.py
1 < Point (1,2) id: 140647337919992 >
2 < Point (1,2) id: 140647337920048 >
3 {< Point (1,2) id: 140647337919992 >}
4 3713081631934410656
5 3713081631934410656
6 False
7 True
8 0x7feb029d85f8 0x7feb029d85f8
9 True
bool 相关的魔术方法
__bool__
and __len__
方法 | 说明 |
---|---|
__bool__ |
内建函数bool()调用,或者对象放在逻辑表达式的位置,也会调用这个函数返回布尔值。 没有定义 __bool__() ,就找 __len__() 返回长度,非0为真。如果 __len__() 也没有定义,那么就找基类的__bool__() ,如果基类中没有定义,就一直找到 object , 而object 对于所有的示例返回都是真 |
class A: pass
print(1,bool(A()))
if A():
print(2,'Real A')
class B:
def __bool__(self):
return False
print(3,bool(B))
print(4,bool(B()))
if B():
print(5,'Real B')
class C:
def __len__(self): # 虽然 类 C 中没有定义 __bool__ 函数,但是有 __len__ 函数,就会调用 __len__ 函数来判断 bool 值
return 0 # 这里 return 0 恒等效为 False
print(6,bool(C()))
if C():
print(7,'Real C')
------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test4.py
1 True
2 Real A
3 True
4 False
6 False
可视化相关的魔术方法
__str__
__repr__
__bytes__
方法 | 说明 |
---|---|
__str__ |
str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。 如果没有定义,就去调用 __repr__ 方法返回字符串表达。如果 __repr__ 没有定义,就直接返回对象的内存地址信息 |
__repr__ |
内建函数 repr() 对一个对象获取字符串表达。调用 __repr__ 方法返回字符串表达如果 __repr__ 也没有定义,就直接返回object的定义,也就是显示内存地址信息 |
__bytes__ |
bytes() 函数调用,返回一个对象的bytes表达,即一定要返回一个bytes对象 |
现象总结:
-
如果
A类
中只是定义了__str__
函数,a = A()
- 那么
str(a)
、'{}'.format(a)
、print(a)
都是__str__
方法定义的变现形式 str([a])
、'{}'.format([a])
、print([a])
返回的都是[ a对象的字符串表达 ]
- 也就是说
str()
、format()
、print()
三个函数不能够穿透容器使用容器内部对象对应的类的__str__
方法
- 那么
-
如果
A类
中只是定义了__repe__
函数,a = A()
- 那么
str(a)
、'{}'.format(a)
、print(a)
返回的都是直接使用__repe__
方法定义的变现形式 str([a])
、'{}'.format([a])
、print([a])
返回的都是直接使用__repe__
方法定义的变现形式- 也就是说
str()
、format()
、print()
三个函数能够穿透容器使用容器内部对象对应的类的__repe__
方法
- 那么
-
如果
A类
中同时定义了__str__
和__repe__
函数,a = A()
- 那么
str(a)
、'{}'.format(a)
、print(a)
都是__str__
方法定义的变现形式 str([a])
、'{}'.format([a])
、print([a])
返回的都是__repe__
方法定义的变现形式- 也就是说
str()
、format()
、print()
三个函数能够穿透容器使用容器内部对象对应的类的__repe__
方法
- 那么
示例 1 只定义了 __str__
方法
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __str__(self):
return 'str: {},{}'.format(self.name, self.age)
a = A('scm')
print(1,a)
print(2,"{}".format(a))
print(3,str(a))
print('=' * 30)
print(4,[a])
print(5,"{}".format([a]))
print(6,str([a]))
print('=' * 30)
print(7,{a})
print(8,"{}".format((a,)))
print(9,str({"aa":{"aaa":a}}))
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 str: scm,18
2 str: scm,18
3 str: scm,18
==============================
4 [<__main__.A object at 0x7f6e4efbd668>]
5 [<__main__.A object at 0x7f6e4efbd668>]
6 [<__main__.A object at 0x7f6e4efbd668>]
==============================
7 {<__main__.A object at 0x7f6e4efbd668>}
8 (<__main__.A object at 0x7f6e4efbd668>,)
9 {'aa': {'aaa': <__main__.A object at 0x7f6e4efbd668>}}
示例 2 只定义了 __repe__
方法
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __repr__(self):
return 'repr: {},{}'.format(self.name, self.age)
a = A('scm')
print(1,a)
print(2,"{}".format(a))
print(3,str(a))
print('=' * 30)
print(4,[a])
print(5,"{}".format([a]))
print(6,str([a]))
print('=' * 30)
print(7,{a})
print(8,"{}".format((a,)))
print(9,str({"aa":{"aaa":a}}))
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 repr: scm,18
2 repr: scm,18
3 repr: scm,18
==============================
4 [repr: scm,18]
5 [repr: scm,18]
6 [repr: scm,18]
==============================
7 {repr: scm,18}
8 (repr: scm,18,)
9 {'aa': {'aaa': repr: scm,18}}
示例 3 同时定义了 __str__
方法和 __repe__
方法
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __str__(self):
return 'str: {},{}'.format(self.name, self.age)
def __repr__(self):
return 'repr: {},{}'.format(self.name, self.age)
a = A('scm')
print(1,a)
print(2,"{}".format(a))
print(3,str(a))
print('=' * 30)
print(4,[a])
print(5,"{}".format([a]))
print(6,str([a]))
print('=' * 30)
print(7,{a})
print(8,"{}".format((a,)))
print(9,str({"aa":{"aaa":a}}))
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 str: scm,18
2 str: scm,18
3 str: scm,18
==============================
4 [repr: scm,18]
5 [repr: scm,18]
6 [repr: scm,18]
==============================
7 {repr: scm,18}
8 (repr: scm,18,)
9 {'aa': {'aaa': repr: scm,18}}
示例4 __bytes__
示例
正确示例
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __bytes__(self):
return '<bytes> name:{}, age:{}'.format(self.name, self.age).encode()
a = A('scm', 23)
print(bytes(a))
------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
b'<bytes> name:scm, age:23'
错误示例(返回的不是一个bytes)
class A:
def __init__(self, name, age=18):
self.name = name
self.age = age
def __bytes__(self):
return '<bytes> name:{}, age:{}'.format(self.name, self.age)
a = A('scm', 23)
print(bytes(a))
------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
Traceback (most recent call last):
File "/data/test1/test.py", line 11, in <module>
print(bytes(a))
TypeError: __bytes__ returned non-bytes (type str)
示例 5 综合示例
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, '{}'.format(A('tom'))) # format函数使用__str__
print(3, [A('tom')]) # []使用__str__,但其内部使用__repr__
print(4, [str(A('tom'))]) # []使用__str__,其中的元素使用str()函数也调用__str__
print(5, 'str:a,1') # 字符串直接输出没有引号
s = '1'
print(6, s) # s 是 str 类的实例并不是 A类的实例
s1 = 'a'
print(7, s1) # s1 是 str 类的实例并不是 A类的实例
print(8, [s1],(s,)) # 字符串在基本数据类型内部输出有引号
print(9, {s, 'a'}) # 字符串在基本数据类型内部输出有引号
print(10, bytes(A('tom')))
--------------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 str: tom,18
2 str: tom,18
3 [repr: tom,18]
4 ['str: tom,18']
5 str:a,1
6 1
7 a
8 ['a'] ('1',)
9 {'1', 'a'}
10 b'tom is 18' #
10 b'{"name": "tom", "age": 18}' # json 返回的就结果
注意
不能通过判断是否带引号来判断输出值的类型,类型判断要使用 type
或 isinstance
运算符重载 相关的魔术方法
operator模块 提供以下的特殊方法,可以将类的实例
使用下面的操作符
来操作
运算符 | 特殊方法 | 说明 |
---|---|---|
<, <=, ==, >, >=, != | __lt__ , __le__ , __eq__ , __gt__ , __ge__ , __ne__ |
比较运算符 |
**+, -, *, /, %, //, , divmod | __add__ , __sub__ , __mul__ , __truediv__ , __mod__ , __floordiv__ , __pow__ , __divmod__ |
算数运算符,移位、位运算也有对应的方法 |
**+=, -=, *=, /=, %=, //=, = | __iadd__ , __isub__ , __imul__ , __itruediv__ , __imod__ , __ifloordiv__ , __ipow__ |
__isub__
and __sub__
方法
__isub__
方法定义,一般会 in-place就地来修改自身
如果没有定义 __isub__
方法,则会调用 __sub__
示例:实现A类的2个实例相减
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): # 如果没有定义__isub__,则会调用__sub__
return A(self.name, self - other)
tom = A('tom')
jerry = A('jerry', 16)
ret = tom - jerry
print(1, ret,type(ret))
print(2, jerry - tom, jerry.__sub__(tom))
print(3, id(tom))
tom -= jerry
print(4, id(tom), tom.age)
----------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 2 <class 'int'>
2 -2 -2
3 140060791588624
4 140060791588792 2
__iadd__
and __add__
方法
练习:
完成Point类设计,实现判断点相等的方法,并完成向量的加法
在直角坐标系里面,定义原点为向量的起点.两个向量和与差的坐标分别等于这两个向量相应坐标的和与差若向量的表示为(x,y)形式,
A(X1,Y1) B(X2,Y2),则A+B=(X1+X2,Y1+Y2),A-B=(X1-X2,Y1-Y2)
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(1, points[0].add(points[1]))
# 运算符重载
print(2, points[0] + points[1])
print(3, p1 == p2)
--------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 (2, 2)
2 <Point: 2,2>
3 True
运算符重载中的反向方法 __radd__
a,b 分别是 类A,类B 的实例,b + a
等价于 b.__add__(a)
,如果 类B中 没有实现 __add__
方法,就去找 实例 a 的 __radd__
方法(也就是类 A 中的 __radd__
方法)
1 + a 等价于 1.__add__(a)
,而 int类型 实现了 __add__
方法的,不过这个方法对于这种加法的返回值是 NotImplemented
,解释器发现是这个值,就会发起对第二操作对象 a
的 __radd__
方法的调用。
前面学习过运算符重载的方法,例如 __add__
和 __iadd__
同一类型的 +
调用的是自己类型的 __add__
同一类型的 +=
调用的是自己类型的 __iadd__
class A:
def __init__(self, x):
self.x = x
def __add__(self, other):
print(self, 'add')
return self.x + other.x
def __iadd__(self, other):
print(self, 'iadd')
return A(self.x + other.x)
def __radd__(self, other):
print(self, 'radd')
return self.x + other.x
a = A(4)
b = A(5)
print(a, b)
print('========================= 1 ===========================')
print(a + b)
print('========================= 2 ===========================')
print(b + a)
print('========================= 3 ===========================')
b += a
print('========================= 4 ===========================')
a += b
----------------运行结果----------------
C:\python36\python.exe D:/pyporject/scm/web/test.py
<__main__.A object at 0x000001F87FAE92E8> <__main__.A object at 0x000001F87FC83908>
========================= 1 ===========================
<__main__.A object at 0x000001F87FAE92E8> add
9
========================= 2 ===========================
<__main__.A object at 0x000001F87FC83908> add
9
========================= 3 ===========================
<__main__.A object at 0x000001F87FC83908> iadd
========================= 4 ===========================
<__main__.A object at 0x000001F87FAE92E8> iadd
__radd__
方法根本没有执行过,为什么?
因为都是 A
的实例,都是调用的 __add__
,无非就是实例 a 还是 b 调用而已。
a + 1
测试一下 a + 1
class A:
def __init__(self, x):
self.x = x
def __add__(self, other):
print(self, 'add')
return self.x + other.x
def __iadd__(self, other):
print(self, 'iadd')
return A(self.x + other.x)
def __radd__(self, other):
print(self, 'radd')
return self.x + other.x
a = A(6)
print(a)
print('========================= 1 ===========================')
print(a + 1)
--------------------运行结果--------------------
C:\python36\python.exe D:/pyporject/scm/web/test.py
<__main__.A object at 0x000001CB7FFD92E8>
Traceback (most recent call last):
========================= 1 ===========================
<__main__.A object at 0x000001CB7FFD92E8> add # 这次执行的是 radd
File "D:/pyporject/scm/web/test.py", line 20, in <module>
print(a + 1)
File "D:/pyporject/scm/web/test.py", line 7, in __add__
return self.x + other.x
AttributeError: 'int' object has no attribute 'x'
出现了 AttributeError
,因为 1是int类型,没有 x
这个属性,还是 __add__
被执行了。
1 + a
测试 1 + a
,运行结果如下
class A:
def __init__(self, x):
self.x = x
def __add__(self, other):
print(self, 'add')
return self.x + other.x
def __iadd__(self, other):
print(self, 'iadd')
return A(self.x + other.x)
def __radd__(self, other):
print(self, 'radd')
return self.x + other.x
a = A(6)
print(a)
print('========================= 2 ===========================')
print(1 + a)
--------------------运行结果--------------------
C:\python36\python.exe D:/pyporject/scm/web/test.py
Traceback (most recent call last):
<__main__.A object at 0x00000297DFDF92E8>
File "D:/pyporject/scm/web/test.py", line 22, in <module>
========================= 2 ===========================
print(1 + a)
<__main__.A object at 0x00000297DFDF92E8> radd # 这次执行的是 radd
File "D:/pyporject/scm/web/test.py", line 15, in __radd__
return self.x + other.x
AttributeError: 'int' object has no attribute 'x'
这次执行的是实例 a 的 __radd__
方法。
1 + a
等价于 1.__add__(a)
,也就是 int.__add__(1, a)
,而 int 类型实现了 __add__
方法的,为什么却不抛出异常,而是执行了实例 a 的 __radd__
方法?
再看一个例子
class A:
def __init__(self, x):
self.x = x
def __add__(self, other):
print(self, 'add')
return self.x + other.x
def __iadd__(self, other):
print(self, 'iadd')
return A(self.x + other.x)
def __radd__(self, other):
print(self, 'radd')
return self.x + other.x
class B: # 未实现__add__
def __init__(self, x):
self.x = x
a = A(3)
b = B(9)
print('========================= 1 ===========================')
print(a + b)
print('========================= 2 ===========================')
print(b + a)
--------------------运行结果--------------------
C:\python36\python.exe D:/pyporject/scm/web/test.py
========================= 1 ===========================
<__main__.A object at 0x0000022A8EC63908> add
12
========================= 2 ===========================
<__main__.A object at 0x0000022A8EC63908> radd
12
b + a 等价于 b.__add__(a)
,但是 类B 没有实现 __add__
方法,就去找 a 的 __radd__
方法
1 + a 等价于 1.__add__(a)
,而 int类型 实现了 __add__
方法的,不过这个方法对于这种加法的返回值是 NotImplemented
,解释器发现是这个值,就会发起对第二操作对象的 __radd__
方法的调用。
B类 也等价于下面的实现
class B:
def __init__(self, x):
self.x = x
def __add__(self, other):
if isinstance(other, type(self)):
return self.x + other.x
else:
return NotImplemented
1 + a 能解决吗?
class A:
def __init__(self, x):
self.x = x
def __add__(self, other):
print(self, 'add')
if hasattr(other, 'x'):
return self.x + other.x
else:
try:
x = int(other)
except:
x = 0
return self.x + x
def __iadd__(self, other):
print(self, 'iadd')
return A(self.x + other.x)
def __radd__(self, other):
print(self, 'radd')
return self + other
class B:
def __init__(self, x):
self.x = x
a = A(4)
b = B(10)
print(a + b)
print(b + a)
print(a + 2)
print(2 + a)
print(a + 'abc')
print('abc' + a)
-----------------------------------------------------
C:\python36\python.exe D:/pyporject/scm/web/test.py
<__main__.A object at 0x000001E3C210E080> add
None
<__main__.A object at 0x000001E3C210E080> radd
<__main__.A object at 0x000001E3C210E080> add
None
<__main__.A object at 0x000001E3C210E080> add
6
<__main__.A object at 0x000001E3C210E080> radd
<__main__.A object at 0x000001E3C210E080> add
6
<__main__.A object at 0x000001E3C210E080> add
4
<__main__.A object at 0x000001E3C210E080> radd
<__main__.A object at 0x000001E3C210E080> add
4
'abc' + a
,字符串也实现了 __add__
方法,不过默认是处理不了和其他类型的加法,就返回 NotImplemented
。
运算符重载应用场景
往往是用面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式。
例如,上例中的对+
进行了运算符重载,实现了Point类的二元操作,重新定义为Point + Point
。
提供运算符重载,比直接提供加法方法要更加适合该领域内使用者的习惯。
int类,几乎实现了所有操作符,可以作为参考。
@functools.total_ordering 装饰器
__lt__
,__le__
,__eq__
,__gt__
,__ge__
是比较大小必须实现的方法,但是全部写完太麻烦,使用@functools.total_ordering 装饰器就可以大大简化代码。- 但是要求
__eq__
必须实现,其它方法__lt__
,__le__
,__gt__
,__ge__
实现其一
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)
print(tom < jerry)
print(tom >= jerry) #
print(tom <= jerry) #
----------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
True
False
True
False
上例中大大简化代码,但是一般来说比较实现等于或者小于方法也就够了,其它可以不实现,所以这个装饰器只是看着很美好,且可能会带来性能问题,建议需要什么方法就自己创建,少用这个装饰器。
建议使用下面的方法
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) # 通过 __gt__ 判断
print(tom < jerry) # 通过 __gt__ 判断
print(tom >= jerry) # 通过 __ge__ 判断
print(tom <= jerry) # 通过 __ge__ 判断
print(tom == jerry) # 通过 __eq__ 判断
print(tom != jerry) # 通过 __eq__ 判断
__eq__
等于可以推断不等于__gt__
大于可以推断小于__ge__
大于等于可以推断小于等于
也就是用3个方法,就可以把所有比较解决了,所以total_ordering可以不使用
容器相关方法
__len__
__iter__
__contains__
__getitem__
__setitem__
__missing__
__reversed__
方法 | 说明 |
---|---|
__len__ |
内建函数len()调用,返回对象的长度(>=0的整数 )如果把对象当做容器类型看,就如同list或者dict。 bool()函数调用的时候,如果没有 __bool__() 方法,则会看 __len__() 方法是否存在,存在返回,非0为真 |
__iter__ |
迭代容器时,调用,返回一个新的迭代器对象 |
__contains__ |
in 成员运算符,如果当前类中没有实现,就调用 __iter__ 方法遍历 |
__getitem__ |
实现 self[key] 访问。序列对象,key接受整数为索引,或者切片。对于set和dict,key为 hashable。key不存在引发KeyError异常 |
__setitem__ |
和 __getitem__ 的访问类似,是设置值的方法 |
__missing__ |
字典或其子类使用 __getitem__() 调用时,key不存在执行该方法 |
__reversed__ |
供内建函数 reversed() 调用,实施反转迭代容器中的元素操作。返回的应该是一个新的迭代器对象,其中所有元素的顺序和原来容器相反。如果类中没有定义 __reversed__ 方法,reversed() 内建函数将会调用 __len__() 和 __getitem__() 两个函数实现反转。 |
__reversed__
演示
class A:
def __init__(self):
self.items = [1, 2, 3, 4]
def __reversed__(self):
yield from reversed(self.items) # 这里使用reversed并不会造成无限递归,因为reversed是对 self.items 进行操作的,并不是对 A的实例 a 进行 reversed.
# return reversed(self.items) # 也可以
a = A()
ret = reversed(a)
for i in ret:
print(i, end='\t')
-----------------------------------------------
C:\python36\python.exe D:/pyporject/scm/test4.py
4 3 2 1
__missing__
拦截dict中的 key 不存在的异常,并将返回值返回
class A(dict):
def __missing__(self, key):
print('Missing key : ', key)
return 'scm'
a = A()
print("""return is '{}'""".format(a['k']))
----------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
Missing key : k
return is 'scm'
class A(dict):
def __missing__(self, key):
print('Missing key : ', key)
return 0
a = A()
print("""return is '{}'""".format(a['k']))
----------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
Missing key : k
return is '0'
思考
为什么空字典、空字符串、空元组、空集合、空列表等可以等效为False?
容器化魔术方法 综合应用(购物车练习)
将购物车类改造成方便操作的容器类
class Cart:
def __init__(self):
self.items = []
def additem(self,item):
self.items.append(item)
def __add__(self, other):
self.additem(other)
return self # 这里return了self, 所以就可以实现链式编程
# def __contains__(self, item): # 如果没有实现就调用 __iter__ 方法
# return item in self.items
def __len__(self):
return len(self.items) # 当前类中如果没有实现 __bool__ 方法,就会来找 __len__ 方法
def __iter__(self):
# yield from self.items
return iter(self.items)
def __repr__(self):
return '{}'.format(list(self.items))
def __getitem__(self, item): # 经试验,这里的 item 对于列表操作操作的时候,其实就是 index
return self.items[item]
def __setitem__(self, key, value): # 由于本示例中是对 self.items 这个列表进行操作,所以不能索引超界
self.items[key] = value
c = Cart()
c + 'phone' + 'apple' # 可以实现链式编程
c.__add__('sss').__add__('xxx') # 可以实现链式编程
c.additem('cookies')
print(1, c) # 可以像打印列表一样打印
print(2, list(c)) # 可以迭代
print(3, 'apple' in c) # 可以进行 in 操作
print(4, 'orange' in c) # 可以进行 in 操作
c[1] = 'cat' # 可以通过索引赋值,和列表一样,但是注意不能索引超界
print(5, c)
print(6, c[0]) # 可以索引
print(7, len(c)) # 可以返回长度
print(8, bool(c)) # 可以求bool
------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 ['phone', 'apple', 'sss', 'xxx', 'cookies']
2 ['phone', 'apple', 'sss', 'xxx', 'cookies']
3 True
4 False
5 ['phone', 'cat', 'sss', 'xxx', 'cookies']
6 phone
7 5
8 True
类似字典的容器化实验
class A:
def __init__(self):
self.d = {}
def __getitem__(self, key):
return self.d[key]
def __setitem__(self, key, value):
self.d[key] = value
def __iter__(self):
yield from self.d
def __call__(self, *args, **kwargs):
for item in args:
# print(item)
self.d.update(item) if isinstance(item,dict) else self.d.update((item,))
self.d.update(kwargs)
return self
a = A()
a(("name", 'scm'),("country", 'Amairca'))
a['age'] = 12
a({"b":1,'c':2})
a(dd=123, cc=456)
print(1, '>>> name is: {}'.format(a['name']))
print(2, '>>> age is: {}'.format(a['age']))
print(a.__dict__)
----------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 >>> name is: scm
2 >>> age is: 12
{'d': {'name': 'scm', 'country': 'Amairca', 'age': 12, 'b': 1, 'c': 2, 'dd': 123, 'cc': 456}}
Process finished with exit code 0
可调用对象 相关的魔术方法
什么是可调用对象 以及 __call__
凡是可以在后面加 圆括号
的对象都是可调用对象,圆括号
就是调用的操作符号
1 类是可调用对象
类是可调用对象,原因就是在object类中有 __call__
方法
class A: pass
print(callable(A))
print(object.__call__)
------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
True
<method-wrapper '__call__' of type object at 0x88a480>
2 函数也是可调用对象
函数(function的实例对象
)是可调用对象,原因就是在function
类中有 __call__
方法
def fn():
pass
print(fn.__class__)
print(fn.__class__.__call__)
--------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
<class 'function'>
<slot wrapper '__call__' of 'function' objects>
Python中一切皆对象,函数也不例外。
def foo():
print(foo.__module__, foo.__name__)
foo()
# 等价于
foo.__call__()
函数即对象,对象 foo
加上 ()
,就是调用此函数对象的 __call__()
方法
可调用对象
方法 | 说明 |
---|---|
__call__ |
类中定义一个该方法,实例就可以像函数一样调用 |
可调用对象:定义一个类,并实例化得到其实例,将实例像函数一样调用。
将一个自定义类的实例
变成一个可调用对象
示例 1
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)
print(p()) # 这里的实例 p 就是可调用对象
--------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
<__main__.Point object at 0x7f508cc1f438>
<Point 4:5>
示例2
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))
print(adder.ret)
--------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
15
15
魔术方法实现递归高效版的斐波那契数列
定义一个斐波那契数列的类,方便调用,计算第n项。
增加迭代的方法、返回容器长度、支持索引的方法。
写法 1
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(len(self.items), index+1):
self.items.append(self.items[i-1] + self.items[i-2])
return self.items[index]
print(Fib()(101))
--------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
573147844013817084101
写法 2
class Fib:
def __init__(self):
self.nums = [0, 1, 1]
def __len__(self):
return len(self.nums)
def __getitem__(self, index): # 如果是自动不全的话,这里是 item, 其实就是列表的index,字典就是key
if index < 0:
raise Exception('Invalid index {}'.format(index))
if index >= len(self): # 4
self.nums.append(self[index-1] + self[index-2])
return self.nums[index]
def fib(self, n):
return self[n]
fib = Fib()
print(fib[101])
--------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
573147844013817084101
上例中,增加迭代的方法、返回容器长度、支持索引的方法
class Fib:
def __init__(self):
self.nums = [0, 1, 1]
def __len__(self):
return len(self.nums)
def __getitem__(self, index): # 如果是自动不全的话,这里是 item, 其实就是列表的index,字典就是key
if index < 0:
raise Exception('Invalid index {}'.format(index))
if index >= len(self): # 4
self.nums.append(self[index-1] + self[index-2])
return self.nums[index]
def __iter__(self):
# yield from self.nums
return iter(self.nums)
def __call__(self, index):
return self[index]
def fib(self, n):
return self[n]
def __str__(self):
return "{}".format(self.nums)
__repr__ = __str__
fib = Fib()
print(1, fib[101])
print(2, fib.fib(101))
print(3, list(fib)[95:])
print(4, fib(101))
print(5, fib)
print(6, repr(fib))
------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 573147844013817084101
2 573147844013817084101
3 [31940434634990099905, 51680708854858323072, 83621143489848422977, 135301852344706746049, 218922995834555169026, 354224848179261915075, 573147844013817084101]
4 573147844013817084101
5 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050, 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676221, 23416728348467685, 37889062373143906, 61305790721611591, 99194853094755497, 160500643816367088, 259695496911122585, 420196140727489673, 679891637638612258, 1100087778366101931, 1779979416004714189, 2880067194370816120, 4660046610375530309, 7540113804746346429, 12200160415121876738, 19740274219868223167, 31940434634990099905, 51680708854858323072, 83621143489848422977, 135301852344706746049, 218922995834555169026, 354224848179261915075, 573147844013817084101]
6 [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050, 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676221, 23416728348467685, 37889062373143906, 61305790721611591, 99194853094755497, 160500643816367088, 259695496911122585, 420196140727489673, 679891637638612258, 1100087778366101931, 1779979416004714189, 2880067194370816120, 4660046610375530309, 7540113804746346429, 12200160415121876738, 19740274219868223167, 31940434634990099905, 51680708854858323072, 83621143489848422977, 135301852344706746049, 218922995834555169026, 354224848179261915075, 573147844013817084101]
Process finished with exit code 0
可以看出使用类来实现斐波那契数列也是非常好的实现,还可以缓存数据,便于检索。
关于魔术方法容易引起无限递归的说明
下面的代码就是由于错误使用操符号引起的无限递归异常
class A:
def __getitem__(self, item):
self.lst = [1, 2]
return self[0]
a = A()
print(a[0])
--------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
Traceback (most recent call last):
File "/data/test1/test.py", line 16, in <module>
print(a[0])
File "/data/test1/test.py", line 13, in __getitem__
return self[0]
File "/data/test1/test.py", line 13, in __getitem__
return self[0]
File "/data/test1/test.py", line 13, in __getitem__
return self[0]
[Previous line repeated 330 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
问题分析
- 首先
[]
操作符就是调用类中的__getitem__
方法 - 不能在
__getitem__
方法内部 对当前类的实例本身使用[]
操作符号,否则就是死递归 - 可以在
__getitem__
方法内部 对其他类的实例使用[]
操作符号
正确的使用方法
class A:
def __getitem__(self, item):
self.lst = [1, 2]
return self.lst[0]
a = A()
print(a[0])
------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1
说明
本例仅仅是以__getitem__
魔术方法和[]
操作符号为例,其他魔术方法和操作符的配合同理。
上下文管理相关的魔术方法
文件IO操作可以对文件对象使用上下文管理,使用 with...as
语法。
with open('access.log') as f:
pass
仿照上例写一个自己的类,实现上下文管理
class Point:
pass
with Point() as p: # AttributeError: __exit__
pass
----------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
Traceback (most recent call last):
File "/data/test1/test.py", line 4, in <module>
with Point() as p: # AttributeError: __exit__
AttributeError: __enter__
提示属性错误,没有 __exit__
,看了需要这个属性
某些版本的python会显示没有 __enter__
上下文管理对象以及 __enter__()
and __exit__()
方法
当一个对象同时实现了 __enter__()
和 __exit__()
方法,它就属于上下文管理的对象
方法 | 意义 |
---|---|
__enter__ |
进入与此对象相关的上下文。 如果存在该方法, with 语法会 把该方法的返回值作为绑定到 as 子句中指定的变量上 |
__exit__ |
退出与此对象相关的上下文。 |
import time
class Point:
def __init__(self):
print(1, 'init +++++++++')
time.sleep(0.5)
print(2, '#--------')
def __enter__(self):
print(3, 'enter =========')
def __exit__(self, exc_type, exc_val, exc_tb):
print(4, 'exit *********')
with Point() as p:
print(5,'in with block $$$$$$$$$')
time.sleep(0.5)
print(6,'will out with block %%%%%%%%%')
print('^^^^^^^Every thing is over^^^^^^^^')
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 init +++++++++
2 #--------
3 enter =========
5 in with block $$$$$$$$$
6 will out with block %%%%%%%%%
4 exit *********
^^^^^^^Every thing is over^^^^^^^^
Process finished with exit code 0
- 实例化对象的时候,并不会调用
__enter__
,进入with语句块
才会调用__enter__
方法,然后执行with语句块中的语句体,最后离开with语句块
的时候,调用__exit__
方法。 with
可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。 这很类似装饰器的效果- 注意,
with
并 不开启 一个新的作用域。只有函数和类以及模块才会有作用域的概念。
上下文管理的安全性
异常对上下文安全的影响
import time
class Point:
def __init__(self):
print(1, 'init +++++++++')
time.sleep(0.5)
print(2, '#--------')
def __enter__(self):
print(3, 'enter =========')
def __exit__(self, exc_type, exc_val, exc_tb):
print(4, 'exit *********')
with Point() as p:
print(5,'in with block $$$$$$$$$')
raise Exception('problem')
time.sleep(0.5)
print(6,'will out with block %%%%%%%%%')
print('^^^^^^^Every thing is over^^^^^^^^')
--------------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 init +++++++++
Traceback (most recent call last):
File "/data/test1/test.py", line 17, in <module>
raise Exception('problem')
Exception: problem
2 #--------
3 enter =========
5 in with block $$$$$$$$$
4 exit *********
Process finished with exit code 1
可以看出即使在with
语句块中出现了异常,__enter__
和__exit__
照样执行,所以说上下文管理是安全的。
程序退出对上下文安全的影响
极端的例子
调用 sys.exit()
,它会退出当前解释器。
打开Python解释器,在里面敲入sys.exit()
,窗口直接关闭了。也就是说碰到这一句,Python运行环境直接退出了。
import time
class Point:
def __init__(self):
print(1, 'init +++++++++')
time.sleep(0.5)
print(2, '#--------')
def __enter__(self):
print(3, 'enter =========')
def __exit__(self, exc_type, exc_val, exc_tb):
print(4, 'exit *********')
with Point() as p:
print(5,'in with block $$$$$$$$$')
import sys
sys.exit(255)
time.sleep(0.5)
print(6,'will out with block %%%%%%%%%')
print('^^^^^^^Every thing is over^^^^^^^^')
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
1 init +++++++++
2 #--------
3 enter =========
5 in with block $$$$$$$$$
4 exit *********
Process finished with exit code 255 # 这里就是系统的返回值
从执行结果来看,依然执行了__exit__
函数,哪怕是退出Python运行环境。上下文管理是安全的。
说明上下文管理很安全。
with语句的执行流程演示
import time
class Point:
def __init__(self):
print(1, 'init +++++++++')
def __enter__(self):
print(2, 'enter =========')
def __exit__(self, exc_type, exc_val, exc_tb):
print(3, 'exit *********')
f = open('test6.py')
with f as p:
print(4, f)
print(5, p)
print(f is p)
print(f == p)
print('=' * 50)
p = Point()
with p as f:
print(6, p == f)
print(7, p)
print(8, f) # 居然是 None
print('^^^^^^^Every thing is over^^^^^^^^')
------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
4 <_io.TextIOWrapper name='test6.py' mode='r' encoding='UTF-8'>
5 <_io.TextIOWrapper name='test6.py' mode='r' encoding='UTF-8'>
True
True
==================================================
1 init +++++++++
2 enter =========
6 False
7 <__main__.Point object at 0x7f1ee4c0dbe0>
8 None
3 exit *********
^^^^^^^Every thing is over^^^^^^^^
Process finished with exit code 0
为什么
f
是 None
问题在于__enter__
方法上,它将自己的返回值赋给f
。
- 以上问题的解决方法是,应该将示例对象
self
通过__enter__
方法返回 - 这样就可以通过后面的
as
语句将 改返回的对象赋值给f
- 到底
__enter__
方法返回值是什么,是否使用as
语句,可以根据实际情况而定。
import time
class Point:
def __init__(self):
print(1, 'init +++++++++')
def __enter__(self):
print(2, 'enter =========')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(3, 'exit *********')
f = open('test6.py')
with f as p:
print(4, f)
print(5, p)
print(f is p)
print(f == p)
print('=' * 50)
p = Point()
with p as f:
print(6, p == f)
print(7, p)
print(8, f)
print('^^^^^^^Every thing is over^^^^^^^^')
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
4 <_io.TextIOWrapper name='test6.py' mode='r' encoding='UTF-8'>
5 <_io.TextIOWrapper name='test6.py' mode='r' encoding='UTF-8'>
True
True
==================================================
1 init +++++++++
2 enter =========
6 True
7 <__main__.Point object at 0x7f6da5c54be0>
8 <__main__.Point object at 0x7f6da5c54be0>
3 exit *********
^^^^^^^Every thing is over^^^^^^^^
Process finished with exit code 0
with
语法,会调用with后的对象
的__enter__
方法,如果有 as
,则将该方法的返回值赋给 as
子句的变量。
__enter__
and __exit__
方法的参数
-
__enter__
方法 没有其他参数。 -
__exit__
方法有3个参数:__exit__(self, exc_type, exc_value, traceback)
-
这三个参数都与异常有关。
- 如果该上下文退出时没有异常,这3个参数都为
None
。 - 如果有异常,参数意义如下
exc_type
,异常类型exc_value
,异常的值traceback
,异常的追踪信息
- 如果该上下文退出时没有异常,这3个参数都为
-
__exit__
方法返回一个等效True
的值,则压制异常
;否则,继续抛出异常
上下文管理的 压制异常 示例
- 以下代码中 在
__exit__
方法中的return
值等效为 True 的时候就可以压制异常 - 以下代码中 在
__exit__
方法中的return
值等效为 False 的时候就可以压制异常
import time
class Point:
def __init__(self):
print(1, 'init +++++++++')
def __enter__(self):
print(2, 'enter =========')
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(3, exc_type)
print(4, exc_val)
print(5, exc_tb)
# return 'a' # 可以压制异常
# return None # 不可以压制异常
# return 0 # 不可以压制异常
return 1 # 可以压制异常
# return '' # 不可以压制异常
# return [] # 不可以压制异常
# return () # 不可以压制异常
# return {} # 不可以压制异常
print('=' * 50)
p = Point()
with p as f:
print(6, 'inter with block ====')
raise Exception('Problem=======')
print(7, 'will out with block')
print('^^^^^^^Every thing is over^^^^^^^^')
--------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
==================================================
1 init +++++++++
2 enter =========
6 inter with block ====
3 <class 'Exception'>
4 Problem=======
5 <traceback object at 0x7f471b1e7608>
^^^^^^^Every thing is over^^^^^^^^
Process finished with exit code 0
使用上下文管理实现加法函数计时装饰器
- 为加法函数计时
- 方法1、使用装饰器显示该函数的执行时长
- 方法2、使用上下文管理方法来显示该函数的执行时长
加法函数代码
import time
def add(x, y):
time.sleep(2)
return x + y
装饰器实现计时功能
- 最简单的装饰器
import time
import datetime
from functools import wraps, update_wrapper
def clock_decrator(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
""" this is warpper doc """
start = datetime.datetime.now()
ret = fn(*args, **kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print('function <{}> spend {} seconds'.format(fn.__name__, delta))
return ret
return wrapper
@clock_decrator # add = clcok_decrator(add(x,y))
def add(x, y):
""" this is add doc """
time.sleep(0.5)
return x + y
add(1,3)
print(add.__doc__)
------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
function <add> spend 0.501182 seconds
this is add doc
Process finished with exit code 0
上下文实现计时功能
- 通过自定义的上下文管理类的实例实现装饰器
import time
import datetime
from functools import wraps, update_wrapper
class Clock_decrator:
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('total time is {} seconds'.format(delta))
def add(x, y):
""" this is add doc """
time.sleep(1)
return x + y
with Clock_decrator(add) as fn:
fn(1, 3)
print(fn.__doc__)
----------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
this is add doc
total time is 1.001511 seconds
Process finished with exit code 0
另一种实现,使用可调用对象实现。
- 将类的实例变成可调用对象来实现
import time
import datetime
from functools import wraps, update_wrapper
class Clock_decrator:
def __init__(self, fn):
self.fn = fn
##### 最原始的属性拷贝 #####
# self.__doc__ = fn.__doc__
# self.__name__ = fn.__name__
##### 较好的属性拷贝 #####
# update_wrapper(self, fn)
##### 优雅的属性拷贝 #####
wraps(fn)(self)
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
delta = ( datetime.datetime.now() - self.start ).total_seconds()
print('total time is {} seconds'.format(delta))
def __call__(self, *args, **kwargs):
return self.fn(*args, **kwargs)
def add(x, y):
"""this is add doc """
time.sleep(1)
return x + y
with Clock_decrator(add) as fn:
print(fn(1, 3))
print('doc is :',fn.__doc__)
print('name is :', fn.__name__)
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
4
doc is : this is add doc
name is : add
total time is 1.00079 seconds
Process finished with exit code 0
将类打造成装饰器
根据上面的代码,能不能把类当做装饰器用?
import time
import datetime
from functools import wraps, update_wrapper
class Clock_decrator:
def __init__(self, fn):
self.fn = fn
##### 最原始的属性拷贝 #####
# self.__doc__ = fn.__doc__
# self.__name__ = fn.__name__
##### 较好的属性拷贝 #####
# update_wrapper(self, fn)
##### 优雅的属性拷贝 #####
wraps(fn)(self)
def __call__(self, *args, **kwargs):
start = datetime.datetime.now()
ret = self.fn(*args, **kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print('total time is {} seconds'.format(delta))
return ret
@Clock_decrator # add = Clock_decrator(add)
def add(x, y):
"""this is add doc """
time.sleep(2)
return x + y
print(add(1, 3))
print('doc is :', add.__doc__)
print('name is :', add.__name__)
------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
total time is 2.003234 seconds
4
doc is : this is add doc
name is : add
Process finished with exit code 0
关于解决文档字符串拷贝的回顾
- 上面的例子中国我们已经用到了文档字符串拷贝的几种解决方案
- 其实就是我们自己手动实现和利用python的functions模块中提供的update_warpper,或wraps实现
- wraps 其实是通过片函数实现的对update_wrapper的更高级的封装,使得wraps是一个单参的函数,可以作为装饰器
方法一
直接修改 __doc__
class TimeIt:
def __init__(self, fn=None):
self.fn = fn
# 把函数对象的文档字符串赋给类
self.__doc__ = fn.__doc__
方法二
使用functools.wraps函数
import time
import datetime
from functools import wraps, update_wrapper
class Clock_decrator:
def __init__(self, fn):
self.fn = fn
##### 最原始的属性拷贝 #####
# self.__doc__ = fn.__doc__
# self.__name__ = fn.__name__
##### 较好的属性拷贝 #####
# update_wrapper(self, fn)
##### 优雅的属性拷贝 #####
wraps(fn)(self)
def __enter__(self):
self.start = datetime.datetime.now()
return self
def __exit__(self, exc_type, exc_val, exc_tb):
delta = ( datetime.datetime.now() - self.start ).total_seconds()
print('total time is {} seconds'.format(delta))
def __call__(self, *args, **kwargs):
start = datetime.datetime.now()
ret = self.fn(*args, **kwargs)
delta = (datetime.datetime.now() - start).total_seconds()
print('total time is {} seconds'.format(delta))
return ret
@Clock_decrator # add = Clock_decrator(add)
def add(x, y):
"""this is add doc """
time.sleep(2)
return x + y
# print(add(1, 3))
# print('doc is :', add.__doc__)
# print('name is :', add.__name__)
with Clock_decrator(add) as fn:
fn(4,5)
print('doc is :', add.__doc__)
print('name is :', add.__name__)
----------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test.py
total time is 2.00234 seconds
doc is : this is add doc
name is : add
total time is 2.002641 seconds
Process finished with exit code 0
上面的类即可以用在上下文管理,又可以用做装饰器, 不足之处就是还有点重复计时。
上下文应用场景 ***
- 功能增强
在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。 - 资源管理
打开了资源需要关闭,例如文件对象、网络连接、数据库连接等 - 权限验证
在执行代码之前,做权限的验证,在__enter__
中处理
上下文管理装饰器 contextlib.contextmanager
(可以将一个函数装饰成一个上下文管理器)
contextlib.contextmanager
- 它是一个装饰器实现上下文管理,装饰一个函数,而不用像类一样实现
__enter__
和__exit__
方法。 contextlib.contextmanager
对下面被装饰的函数
有要求:- 必须有yield,也就是这个函数必须返回一个生成器。
- 且只有yield一个值。也就是这个装饰器接收一个生成器对象作为参数。
示例1(简单,用来理解contextlib.contextmanager
的工作机制)
import contextlib
@contextlib.contextmanager
def foo(): #
print('enter') # 相当于__enter__()
yield # 相当于yield None, yield的值只能有一个,作为 __enter__ 方法的返回值
print('exit') # 相当于__exit__()
with foo() as f:
#raise Exception()
print(f)
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
enter
None yield 的返回值
exit
import contextlib
@contextlib.contextmanager
def foo(): #
print('enter') # 相当于__enter__()
yield 100 # yield 100, yield的值只能有一个,作为 __enter__ 方法的返回值
print('exit') # 相当于__exit__()
with foo() as f:
raise Exception() # 在这里抛个异常,就会组织上面的 print('exit')
print(f)
-----------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
enter
100 # yield 的返回值
exit
f
接收 yield
语句的返回值。
示例2 增加对异常的处理(python源码中常用)
上面的程序看似不错但是,增加一个异常试一试,发现不能保证exit的执行,怎么办?
增加try
和finally
。
import contextlib
@contextlib.contextmanager
def foo(): #
print('enter') # 相当于__enter__()
try:
yield 100 # yield 5, yield的值只能有一个,作为 __enter__ 方法的返回值
finally:
print('exit') # 相当于__exit__()
with foo() as f:
raise Exception() # 在这里抛出异常,由于上面的代码中的 try 已经捕捉到了异常 所以会打印 print('exit')
print(f)
----------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
Traceback (most recent call last):
File "/data/test1/test5.py", line 12, in <module>
raise Exception()
Exception
enter
exit # 可以看到这里执行了 print('exit'), 所以 finally 后面的代码是一定会被执行的。
上例这么做有什么意义呢?
当yield发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。
- 把yield之前的当做__enter__方法执行
- 把yield之后的当做__exit__方法执行
- 把yield的值作为__enter__的返回值
为add 函数增加上下文管理实现同装饰器的效果
import contextlib
import datetime
import time
@contextlib.contextmanager
def add(x, y): #
start = datetime.datetime.now()
try:
time.sleep(1)
yield x + y # yield 5, yield的值只能有一个,作为 __enter__ 方法的返回值
finally:
delta = (datetime.datetime.now() - start).total_seconds()
print('total spend time is {}'.format(delta)) # 相当于__exit__()
with add(4,5) as f:
# raise Exception() # 在这里抛出异常,由于上面的代码中的 try 已经捕捉到了异常 所以会打印 print('exit')
print(f)
----------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
9
total spend time is 1.000291
上下文管理总结
- 如果业务逻辑简单可以使用函数加contextlib.contextmanager装饰器方式
- 如果业务复杂,用类的方式加
__enter__
和__exit__
方法方便。
反射
反射的概述
运行时,区别于编译时,指的是程序被加载到内存中执行的时候。
反射,reflection
,指的是运行时获取类型定义信息。
一个对象能够在运行时,像照镜子一样,反射出其类型信息。
简单说,在Python中,能够通过一个对象,找出其type
、class
、attribute
或method
的能力,称为反射或者自省。
具有反射能力的函数有 type()
、isinstance()
、callable()
、dir()
、getattr()
等
反射相关的内建函数 getattr(object, name[, default])
setattr(object, name, value)
hasattr(object, name)
引出反射
有一个Point类,查看它实例的属性,并修改它。动态为实例增加属性
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):
return (self.x, self.y)
p = Point(4, 5)
print(1, p)
print(2, p.__dict__)
p.__dict__['z'] = 8
print(3, p.__dict__)
print(4, dir(p)) # ordered list
print(5, p.__dir__()) # list
-------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 <Point 4,5>
2 {'x': 4, 'y': 5}
3 {'x': 4, 'y': 5, 'z': 8}
4 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'show', 'x', 'y', 'z']
5 ['x', 'y', 'z', '__module__', '__init__', '__str__', 'show', '__dict__', '__weakref__', '__doc__', '__repr__', '__hash__', '__getattribute__', '__setattr__', '__delattr__', '__lt__', '__le__', '__eq__', '__ne__', '__gt__', '__ge__', '__new__', '__reduce_ex__', '__reduce__', '__subclasshook__', '__init_subclass__', '__format__', '__sizeof__', '__dir__', '__class__']
Process finished with exit code 0
上例通过属性字典__dict__
来访问对象的属性,本质上也是利用的反射的能力。
但是,上面的例子中,访问的方式不优雅,Python提供了内置的函数。
内建函数 | 说明 |
---|---|
getattr(object, name[, default]) |
通过 name 返回 object的属性值 。当属性不存在,将使用default返回, 如果没有 default ,则抛出AttributeError。name 必须为字符串 |
setattr(object, name, value) |
object 的属性存在,则覆盖,不存在,新增 |
hasattr(object, name) |
判断对象是否有这个名字的属性,name 必须为字符串 |
getattr(object, name[, default])
示例
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)
ret = getattr(Point,'show')
print(1, ret)
Point(6, 7).show()
print('=' * 30)
p = Point(4, 5)
print(2, getattr(p, "x"))
-----------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 <function Point.show at 0x7f966aa32730>
<Point (6,7)>
==============================
2 4
setattr(object, name, value)
示例
用上面的方法来修改上例的代码
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)
setattr(p1, 'sss', 10000) # 为类Point设置属性 sss
print(1, getattr(Point, 'sss'))
print('=' * 30)
p1 = Point(4, 5)
p2 = Point(10, 10)
print(repr(p1), repr(p2), sep='\n')
print(1, p1.__dict__)
setattr(p1, 'y', 16) # 设置属性 y
setattr(p1, 'z', 10) # 设置属性 z
print(2, getattr(p1, '__dict__'))
------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 10000 # 类属性设置成功 新增了属性 sss = 10000
==============================
<__main__.Point object at 0x7fd02eaff780>
<__main__.Point object at 0x7fd02eaff7b8>
1 {'x': 4, 'y': 5}
2 {'x': 4, 'y': 16, 'z': 10} # 设置成功 y 由原来的 5 变成16,新增了属性 z = 16
hasattr(object, name)
示例
# 动态调用方法
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)
print(1, hasattr(Point,'show'))
print(2, hasattr(Point,'x'))
print(3, hasattr(Point,'y'))
print(4, hasattr(Point,'__dict__'))
print(5, hasattr(Point,'__init__'))
print('=' * 30)
p = Point(4, 5)
print(1, hasattr(p, "x"))
-------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 True
2 False
3 False
4 True
5 True
==============================
1 True
动态为 类
增加方法
实例调用该方法为绑定的方法
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(6, 6)
# 动态增加方法
# 为类增加方法
if not hasattr(Point, 'add'):
setattr(Point, 'add', lambda self,other: Point(self.x + other.x, self.y + other.y))
print(1, Point.add)
print(2, p1.add)
print(3, p1.add(p2)) # 绑定
-------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 <function <lambda> at 0x7f5af6e6ce18>
2 <bound method <lambda> of <__main__.Point object at 0x7f5af6dbd748>>
3 <Point (10,11)>
动态为 实例
增加方法
实例调用自己的方法,其他实例没有该方法,该方法不是绑定的方法
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(6, 6)
# 为实例增加方法,未绑定
if not hasattr(p1, 'sub'):
setattr(p1, 'sub', lambda self, other: Point(self.x - other.x, self.y - other.y))
print(1, p1.sub(p1, p1))
print(2, p1.sub)
# add在谁里面,sub在谁里面
print(3, p1.__dict__) # sub 方法在实例字典中
print(4, Point.__dict__) # sub 方法不在类的字典中
--------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 <Point (0,0)>
2 <function <lambda> at 0x7f21212e9e18>
3 {'x': 4, 'y': 5, 'sub': <function <lambda> at 0x7f21212e9e18>}
4 {'__module__': '__main__', '__init__': <function Point.__init__ at 0x7f212122f620>, '__str__': <function Point.__str__ at 0x7f212122f6a8>, 'show': <function Point.show at 0x7f212122f730>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
Process finished with exit code 0
思考
问:这种动态增加属性的方式和装饰器修饰一个类、Mixin方式的差异?
答:
这种动态增删属性的方式是运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了,因此反射能力具有更大的灵活性。
使用反射改进命令分发器
命令分发器,通过名称找对应的函数执行。
思路:名称找对象的方法
class Dispatch:
# def __init__(self, cls):
# self.cls = cls
def add(self, name, fn):
setattr(self, name, fn)
return self
def run(self):
while True:
cmd = input(">>>").strip()
if cmd == 'q':
break
else:
getattr(self, cmd, lambda :print('====Unkonwn Cmd===='))()
reg = Dispatch()
reg.add('ls', lambda :print('------ls------'))
reg.run()
----------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
>>>ls
------ls------
>>>pwd
====Unkonwn Cmd====
>>>
====Unkonwn Cmd====
>>>q
Process finished with exit code 0
上例中使用 getattr
方法找到对象的属性的方式,比自己维护一个字典来建立名称和函数之间的关系的方式好多了。
反射相关的魔术方法 __getattr__()
__setattr__()
__delattr__()
__getattr__()
__setattr__()
__delattr__()
这三个魔术方法跟字面的理解不一样,分别测试这三个方法
__getattr__()
找不到 实例的属性
的时候调用该方法
- 一个类的属性会按照继承关系找,如果找不到,就会执行
__getattr__()
方法,如果没有这个方法,就会抛出AttributeError异常
表示找不到属性。 - 如果调用了
__getattr__()
方法 方法,就会将__getattr__()
方法的返回值返回 - 如果类中定义了
__getattr__()
方法,当查找实例的属性的时候,如果有AttributeError
异常就会去找实例类中的__getattr__()
方法
class Base:
n = 100
class Point(Base):
m = 200
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
pass # __getattr__()方法的返回值是 None
def display(self):
print("<Point ({},{})".format(self.x, self.y))
p = Point(3, 3)
p.display()
print(1, p.x)
print(2, p.y)
print(3, p.z)
print(4, p.m)
print(5, p.n)
------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
<Point (3,3)
1 3
2 3
3 None # __getattr__()方法的返回值是 None
4 200
5 100
class Base:
n = 100
class Point(Base):
m = 200
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
print("====I am __getattr__ ====")
return 1000
def display(self):
print("<Point ({},{})".format(self.x, self.y))
p = Point(3, 3)
p.display()
print(1, p.x)
print(2, p.y)
print(3, p.z)
print(4, p.m)
print(5, p.n)
------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
<Point (3,3)
1 3
2 3
====I am __getattr__ ==== # 为什么会有这条输出? 因为 通过self.z没有获取到实例属性,所以就找到了 __getattr__ 方法
3 1000 # __getattr__()方法的返回值是 1000
4 200
5 100
查找属性顺序为:
instance.__dict__
--> instance.__class__.__dict__
--> 继承的祖先类(直到object)的__dict__
---找不到--> 调用__getattr__()
__setattr__()
通过 .
点号设置 实例属性
的时候调用
实例通过 .
点号设置 实例属性
,例如 self.x = x
,就会调用 __setattr__()
,属性要加到实例的 __dict__
中,就需要自己完成。
class Base:
n = 100
class Point(Base):
m = 200
def __init__(self, x, y):
self.x = x
self.y = y
def __getattr__(self, item):
print("==== I am __getattr__ ====")
return 1000
def __setattr__(self, key, value):
print("**** I am __setattr__ ****")
# self.key = value
def display(self):
print("<Point ({},{})".format(self.x, self.y))
p = Point(3, 3)
p.display()
print(1, p.x)
print(2, p.y)
print(3, p.z)
print(4, p.m)
print(5, p.n)
------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
**** I am __setattr__ **** # 为什么会有这条输出? 因为 __init__ 方法中有通过 self.x = x 对实例属性赋值
**** I am __setattr__ **** # 为什么会有这条输出? 因为 __init__ 方法中有通过 self.y = y 对实例属性赋值
==== I am __getattr__ ==== # 为什么会有这条输出? 因为 __setattr__ 方法中没有对实例self进行赋值操作只是打印了一下,所以self并没有属性 x ,在调用 p.display() 的时候,display方法中有通过 self.x 获取实例的 x 属性,但是没有,就去找 __getattr__ 方法,所以就打印了此行。
==== I am __getattr__ ==== # 原因同上行打印输出原因。
<Point (1000,1000) # 由于 __getattr__ 方法返回的就是 1000,所以最后 点实例的坐标就是 (1000,1000)
==== I am __getattr__ ==== # print(1, p.x) 中使用 self.x 方式访问实例的 x 属性,但是没有,就去找 __getattr__ 并将1000 返回
1 1000 # 1000 就是上一行的执行结果返回湖值
==== I am __getattr__ ==== # print(1, p.y) 中使用 self.y 方式访问实例的 y 属性,但是没有,就去找 __getattr__ 并将1000 返回
2 1000 # 1000 就是上一行的执行结果返回湖值
==== I am __getattr__ ==== # 原因同上
3 1000 # 原因同上
4 200 # 可以在 Point类中找到 m 属性,就不糊会去找 __getattr__ 了
5 100 # 可以在 Base 类中找到 n 属性,就不糊会去找 __getattr__ 了
__setattr__()
方法,可以拦截对实例属性的增加、修改操作,如果要设置生效,需要自己通过操作实例的 __dict__
来然绕过此拦截。
class Base:
n = 100
class Point(Base):
m = 200
def __init__(self, x, y):
pass
# self.x = x # 注释调就不会调用 __getattr__
# self.y = y # 注释调就不会调用 __getattr__
def __getattr__(self, item):
print("==== I am __getattr__ ====")
return 1000
def __setattr__(self, key, value):
print("**** I am __setattr__ ****")
def display(self):
print("<Point ({},{})".format(self.x, self.y))
p = Point(3, 3)
p.__dict__['z'] = 3000
print(3, p.z)
print(p.__dict__)
----------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
3 3000
{'z': 3000}
需要在 __setattr__
方法中同样应该使用 字典的方式设置属性,否则会引起递归调用
class Base:
n = 100
class Point(Base):
m = 200
def __init__(self, x, y):
# pass
self.x = x
self.y = y
def __getattr__(self, item):
print("==== I am __getattr__ ====")
return 1000
def __setattr__(self, key, value):
print("**** I am __setattr__ ****")
# self.key = value # 这种方法会无限递归调用 __setattr__
self.__dict__[key] = value # 应该使用字典的方法设置属性
def display(self):
print("<Point ({},{})".format(self.x, self.y))
p = Point(3, 3)
p.__dict__['z'] = 3000
print(3, p.z)
print(p.__dict__)
----------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
**** I am __setattr__ ****
**** I am __setattr__ ****
3 3000
{'x': 3, 'y': 3, 'z': 3000}
__delattr__()
拦截 通过实例
来删除属性的操作
- 通过
del
语句进行属性的删除的时候就会调用__delattr__()
方法 - 可以拦截通过实例来删除属性的操作。
- 但是通过类依然可以直接删除属性,并不会被
__delattr__()
拦截。 - 在
__delattr__()
方法中进行属性的删除操作也是可以的。 - 需要注意的是在
__delattr__()
方法中属性的删除操作也要使用字典的pop操作,不能使用 del self.item 的方式,否则就会引起递归调用。
class Base:
n = 100
class Point(Base):
m = 200
def __init__(self, x, y):
self.x = x
self.y = y
def __delattr__(self, item):
print("==== I am __delattr__ ====")
self.__dict__.pop(item)
# del self.item # 不能在 __delattr__ 中使用 del 语句进行实例属性删除,否则就会递归调用 __delattr__
def display(self):
print("<Point ({},{})".format(self.x, self.y))
p = Point(3, 3)
p.display()
print(p.__dict__)
del p.x
print(p.__dict__)
----------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
<Point (3,3)
{'x': 3, 'y': 3}
==== I am __delattr__ ====
{'y': 3}
__getattribute__
- 实例的所有的属性访问,第一个都会调用
__getattribute__
方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError
异常。 - 它的 return 值将作为属性查找的结果。
- 如果抛出 AttributeError 异常,则会直接调用
__getattr__
方法,因为表示属性没有找到。
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__)
print(p1.x)
print(p1.z)
print(p1.n)
print(p1.t)
print(Point.__dict__)
print(Point.z)
__getattribute__
方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如 object.__getattribute__(self, name)
。
__getattribute__
方法中使用点操作符号引起的无限递归
class Base:
n = 100
class Point(Base):
m = 200
def __init__(self, x, y):
self.x = x
self.y = y
def __getattribute__(self, item):
return self.item
# return self.__dict__[item] # 也会引起无限递归,因为有 self.__dict__, 也是同通过 点操作符号进行属性的查找。
def __getattr__(self, item):
print("==== I am __getattr__ ====")
p = Point(3, 3)
p.display()
print(p.__dict__)
print(p.__dict__)
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
Traceback (most recent call last):
File "/data/test1/test3.py", line 21, in <module>
p.display()
File "/data/test1/test3.py", line 14, in __getattribute__
return self.item
File "/data/test1/test3.py", line 14, in __getattribute__
return self.item
File "/data/test1/test3.py", line 14, in __getattribute__
return self.item
[Previous line repeated 330 more times]
RecursionError: maximum recursion depth exceeded while calling a Python object
通过 object.__getattribute__(self, name)
解决无限递归问题
class Base:
n = 100
class Point(Base):
m = 200
def __init__(self, x, y):
self.x = x
self.y = y
def __getattribute__(self, item):
# return super().__getattribute__(item) ## 也可以
return object.__getattribute__(self,item)
def __getattr__(self, item):
print("==== I am __getattr__ ====")
p = Point(3, 6)
print(p.__dict__)
print(p.x)
print(p.y)
print(p.z)
----------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
{'x': 3, 'y': 6}
3
6
==== I am __getattr__ ====
None
注意:
除非你明确地知道 __getattribute__
方法用来做什么,否则不要使用它。
反射相关魔术方法总结
魔术方法 | 意义 |
---|---|
__getattr__() |
当通过搜索实例、实例的类及祖先类查不到属性,就会调用此方法 |
__setattr__() |
通过 . 访问实例属性,进行增加、修改都要调用它 |
__delattr__() |
当通过实例来删除属性时调用此方法 |
__getattribute__ |
实例所有的属性调用都从这个方法开始 |
最终版的属性查找顺序
实例调用__getattribute__()
--> instance.__dict__
--> instance.__class__.__dict__
--> 继承的祖先类(直到object)的__dict__
--> 调用__getattr__()
描述器(也叫描述符) Descriptors
描述器的表现
用到3个魔术方法: __get__()
__set__()
__delete__()
方法签名如下
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)
------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
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的值。
描述器定义 以及 __get__()
__set__()
__delete__()
Python中,一个 类
实现了 __get__
、__set__
、__delete__
三个方法中的任何一个方法,就是描述器。实现这三个中的某些方法,就支持了描述器协议。
- 如果一个类
A
中仅实现了__get__
,就是非数据描述器non-data descriptor
; - 如果一个类
A
中同时实现了__get__
、__set__
就是数据描述器data descriptor
。 - 如果另一个类
B
的类属性设置为描述器A
的实例,那么类B
被称为owner
(属主)。 - 当类
B
的类属性被查找、设置、删除时,就会调用描述器A
相应的方法。
非数据描述器 (类中只实现 __get__
方法)
注意实例的属性名要和类的属性同名才行
类A
定义了 __get__
方法,类A
就是一个描述器,类B
的x
属性是类A(描述器)的实例
,那么通过类B
自身或者类B的实例
对的类B
的x属性
读取,成为对类A
的实例的访问,就会调用 __get__
方法,__get__
方法的返回值就是获取的结果,所以可以通过__get__
方法自定义返回什么。
我们对类A做一些改造:在类A中实现 __get__
方法,看看变化
_get__(self, instance, owner)
参数
self
, instance
, owner
这三个参数,是什么意思?
__get__(self, instance, owner)方法的签名,会传入3个参数
self
对应都是A的实例
owner
对应都是B类
instance
说明- None 表示
不是B类的实例
也就是通过B
类自己来调用x
,对应的调用方法为B.x
- <main.B object at 0x7f53caef26d8> 表示是通过
B
类的实例调用x
,对应调用方法为B().x
- None 表示
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __get__(self, instance, owner):
print('A.__get__ {} - {} - {}'.format(self, instance, owner),end = '\t')
return self
class B:
x = A()
print('** x **',x)
def __init__(self):
print('B.init ~~~~~~~~~~~~')
print('=' * 20)
b = B()
# print("1---",b.x)
# print('2 b.x ---- {}'.format(b.x))
print("1---",b.x.a1) # 此种方法 的 instance 为 一个 B类的实例
print("2---",B.x.a1) # 此种方法 的 instance 为 None
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
A.init ~~~~~~~~~~~~
** x ** <__main__.A object at 0x7fe542186668>
====================
B.init ~~~~~~~~~~~~
A.__get__ <__main__.A object at 0x7fe542186668> - <__main__.B object at 0x7fe542186710> - <class '__main__.B'> 1--- a1
A.__get__ <__main__.A object at 0x7fe542186668> - None - <class '__main__.B'> 2--- a1
通过类访问描述器实例
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __get__(self, instance, owner):
print('A.__get__ {} - {} - {}'.format(self, instance, owner), end='\t')
class B:
x = A()
print('** x **',x)
def __init__(self):
print('B.init ~~~~~~~~~~~~')
print('=' * 20)
print("1---", B.x)
print("2---", B.x.a1) # 抛异常AttributeError: 'NoneType' object has no attribute 'a1'
------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
A.init ~~~~~~~~~~~~
** x ** <__main__.A object at 0x7ff4b3ee55f8>
====================
A.__get__ <__main__.A object at 0x7ff4b3ee55f8> - None - <class '__main__.B'> 1--- None
A.__get__ <__main__.A object at 0x7ff4b3ee55f8> - None - <class '__main__.B'> Traceback (most recent call last):
File "/data/test1/test3.py", line 19, in <module>
print("2---", B.x.a1) # 抛异常AttributeError: 'NoneType' object has no attribute 'a1',是因为描述器中的 __get__ 方法返回的是 None,None是没有a1属性的
AttributeError: 'NoneType' object has no attribute 'a1'
Process finished with exit code 1
通过实例访问描述器实例
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __get__(self, instance, owner):
print('A.__get__ {} - {} - {}'.format(self, instance, owner),end = '\t')
return 100
class B:
x = A()
print('** x **',x)
def __init__(self):
print('B.init ~~~~~~~~~~~~')
print('=' * 20)
b = B() # 将B类实例化出一个实例
print("3---",b.x) # 通过实例可以访问到类的非数据描述器属性
print('b.x ---- {}'.format(b.x)) # 看看 b.x 是什么, 可以看到就是 __get__ 方法中返回的 100, 将100 赋给了x
print("4---",b.x.a1) # 抛异常AttributeError: 'int' object has no attribute 'a1',是因为描述器中的 __get__ 方法返回的是 100,100是没有 a1属性的
----------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
Traceback (most recent call last):
File "/data/test1/test5.py", line 21, in <module>
print("4---",b.x.a1) # 抛异常AttributeError: 'NoneType' object has no attribute 'a1',是因为描述器中的 __get__ 方法返回的是 None,None是没有 a1属性的
AttributeError: 'int' object has no attribute 'a1'
A.init ~~~~~~~~~~~~
** x ** <__main__.A object at 0x7f9a9dc0b668>
====================
B.init ~~~~~~~~~~~~
A.__get__ <__main__.A object at 0x7f9a9dc0b668> - <__main__.B object at 0x7f9a9dc0b710> - <class '__main__.B'> 3--- 100
A.__get__ <__main__.A object at 0x7f9a9dc0b668> - <__main__.B object at 0x7f9a9dc0b710> - <class '__main__.B'> b.x ---- 100
A.__get__ <__main__.A object at 0x7f9a9dc0b668> - <__main__.B object at 0x7f9a9dc0b710> - <class '__main__.B'>
解决 AttributeError
通过上面两个例子我们知道 A的实例才有属性a1
, 所以我们要将 A类的实例自己返回才能通过·
B.x.a1
或 b.x.a1
访问到a1属性
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __get__(self, instance, owner):
print('A.__get__ {} - {} - {}'.format(self, instance, owner),end = '\t')
return self
class B:
x = A()
print('** x **',x)
def __init__(self):
print('B.init ~~~~~~~~~~~~')
print('=' * 20)
b = B()
print("1---",b.x)
print('2 b.x ---- {}'.format(b.x))
print("3---",b.x.a1)
print("4---",B.x.a1)
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
A.init ~~~~~~~~~~~~
** x ** <__main__.A object at 0x7f83e84a36a0>
====================
B.init ~~~~~~~~~~~~
A.__get__ <__main__.A object at 0x7f83e84a36a0> - <__main__.B object at 0x7f83e84a3748> - <class '__main__.B'> 1--- <__main__.A object at 0x7f83e84a36a0>
A.__get__ <__main__.A object at 0x7f83e84a36a0> - <__main__.B object at 0x7f83e84a3748> - <class '__main__.B'> 2 b.x ---- <__main__.A object at 0x7f83e84a36a0>
A.__get__ <__main__.A object at 0x7f83e84a36a0> - <__main__.B object at 0x7f83e84a3748> - <class '__main__.B'> 3--- a1
A.__get__ <__main__.A object at 0x7f83e84a36a0> - None - <class '__main__.B'> 4--- a1
Process finished with exit code 0
区分实例的属性和类的描述器
只有一个类的属性
为描述器实例,才可以通过该类
或该类的实例
调用描述器的 __get__
方法
只有类属性
是描述器类的实例才行
, 实例属性
是类的实例不行
。
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __get__(self, instance, owner):
print('A.__get__ {} - {} - {}'.format(self, instance, owner), end='\t')
return self # 解决返回None的问题
class B:
x = A()
def __init__(self):
print('B.init ~~~~~~~~~~~~')
self.y = A() # 实例属性也指向一个A的实例
b = B()
print(1, b.y) # b.y 不能调用 __get__ 方法,因为 self.y = A() 是定义为实例的属性,只有类的属性为描述器实例,才可以调用描述器的 __get__ 方法
print(2, b.x) # b.x 可以调用 __get__ 方法,因为 self.x = A() 是定义为实例的属性,只有类的属性为描述器实例,才可以调用描述器的 __get__ 方法
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
A.init ~~~~~~~~~~~~
B.init ~~~~~~~~~~~~
A.init ~~~~~~~~~~~~
1 <__main__.A object at 0x7f75187fa6d8>
A.__get__ <__main__.A object at 0x7f75187fa550> - <__main__.B object at 0x7f75187fa6a0> - <class '__main__.B'> 2 <__main__.A object at 0x7f75187fa550>
实例属性
和非数据描述器
的优先级比较
当 实例的属性
和 类中的描述器实例
同名的时候,通过实例访问同名属性的时候 实例属性
优先于 非数据描述器
演示1
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __get__(self, instance, owner):
print('A.__get__ {} - {} - {}'.format(self, instance, owner), end='\t')
return self # 解决返回None的问题
class B:
x = A()
def __init__(self):
print('B.init ~~~~~~~~~~~~')
self.y = A() # 实例属性也指向一个A的实例
self.x = 100
b = B()
print(1, b.y)
print(2, b.x) # 会直接访问到 实例的属性,并不会访问到 非数据描述器
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
A.init ~~~~~~~~~~~~
B.init ~~~~~~~~~~~~
A.init ~~~~~~~~~~~~
1 <__main__.A object at 0x7fcb5ea536a0>
2 100 # 会直接访问到 实例的属性,并不会访问到 非数据描述器
演示2
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __get__(self, instance, owner):
print('A.__get__ {} - {} - {}'.format(self, instance, owner), end='\t')
return self # 解决返回None的问题
class B:
x = A()
def __init__(self):
print('B.init ~~~~~~~~~~~~')
self.x = A() # 实例属性也指向一个A的实例
b = B()
print(1, b.x) # 会直接访问到 实例的属性,并不会访问到 非数据描述器
print(2, b.__dict__)
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test5.py
A.init ~~~~~~~~~~~~
B.init ~~~~~~~~~~~~
A.init ~~~~~~~~~~~~
1 <__main__.A object at 0x7ffa0a6096a0> # 会直接访问到 实例的属性,并不会访问到 非数据描述器
2 {'x': <__main__.A object at 0x7ffa0a6096a0>}
数据描述器 (类中同时实现 __get__
和 __set__
方法)
属性查找顺序
- 实例的
__dict__
优先于非数据描述器 - 数据描述器 优先于 实例的
__dict__
__delete__
方法有同样的效果,有了这个方法,也是数据描述器。
类A
同时定义了 __get__
和 __set__
方法,类A
就是一个数据描述器,类B
的x
属性是类A(描述器)的实例
,那么通过 类B的实例
(不能是类B自身) 对的类B的实例
的x属性
设置,就会调用 类A的 __set__
方法。 (需要注意的是,__set__
只能进行设置,不能获取,所以要同时使用__get__方法)
注意实例的属性名要和类的属性同名才行
__set__
方法调用时机的演示
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __set__(self, instance, value):
print('A.__set__ {} - {} - {}'.format(self, instance, value), end='\t')
class B:
x = A()
def __init__(self):
print('B.init ~~~~~~~~~~~~')
self.x = A() # 会调用 A中的 __set__ 方法,因为 self.x 中的 x 与 x = A() 中的 x 同名
# self.x = 100 # 会调用 A中的 __set__ 方法,因为 self.x 中的 x 与 x = A() 中的 x 同名
# self.y = A() # 不会调用 A中的 __set__ 方法,因为 self.y 中的 y 与 x = A() 中的 x 不同名
b = B()
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
A.init ~~~~~~~~~~~~
B.init ~~~~~~~~~~~~
A.init ~~~~~~~~~~~~
A.__set__ <__main__.A object at 0x7f4c06fd65c0> - <__main__.B object at 0x7f4c06fd6668> - <__main__.A object at 0x7f4c06fd66a0>
Process finished with exit code 0
__set__(self, instance, value)
参数
self
, instance
, value
这三个参数,是什么意思?
__set__(self, instance, value)方法的签名,会传入3个参数
示例代码
class A:
def __init__(self):
self.a1 = 'a1'
print('A.init ~~~~~~~~~~~~')
def __set__(self, instance, value):
print('A.__set__ {} - {} - {}'.format(self, instance, value), end='\t')
class B:
x = A()
def __init__(self):
print('B.init ~~~~~~~~~~~~')
# self.x = A() # 可以
self.x = 100 # 可以
b = B()
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
A.init ~~~~~~~~~~~~
B.init ~~~~~~~~~~~~
A.__set__ <__main__.A object at 0x7f7956a525c0> - <__main__.B object at 0x7f7956a52668> - 100
Process finished with exit code 0
self
对应都是A的实例
instance
对应都是B类
value
对应B类
实例属性要设置的值
__set__(self, instance, value)
执行机制
示例代码
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.data
#
def __set__(self, instance, value):
print('A.__set__ {} - {} - {}'.format(self, instance, value))
self.data = value
class B:
x = A()
y = A()
def __init__(self):
print('B.init ~~~~~~~~~~~~')
# self.x = A()
self.x = 100
self.y = 200
# self.y = A()
b = B()
b.x = 222 # 会调用 A类中的 __set__, 不会为 b 添加一个属性 x
b.y = 333 # 会调用 A类中的 __set__, 不会为 b 添加一个属性 y
b.z = 444 # 不会调用 A类中的 __set__, 而是直接为 b 添加一个属性 z
print(" b.x return ---- @<1>@", b.x)
print(" b.y return ---- @<2>@", b.y)
print(" b.z return ---- @<3>@", b.z)
print(" b.__dict__ return ---- @<4>@ b.__dict__ is ", b.__dict__)
print(" B.x return ---- @<5>@", B.x)
print(" B.y return ---- @<6>@", B.y)
------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
A.init ~~~~~~~~~~~~
A.init ~~~~~~~~~~~~
B.init ~~~~~~~~~~~~
A.__set__ <__main__.A object at 0x7eff430c8550> - <__main__.B object at 0x7eff430c86a0> - 100
A.__set__ <__main__.A object at 0x7eff430c8668> - <__main__.B object at 0x7eff430c86a0> - 200
A.__set__ <__main__.A object at 0x7eff430c8550> - <__main__.B object at 0x7eff430c86a0> - 222
A.__set__ <__main__.A object at 0x7eff430c8668> - <__main__.B object at 0x7eff430c86a0> - 333
A.__get__ <__main__.A object at 0x7eff430c8550> - <__main__.B object at 0x7eff430c86a0> - <class '__main__.B'>
b.x return ---- @<1>@ 222
A.__get__ <__main__.A object at 0x7eff430c8668> - <__main__.B object at 0x7eff430c86a0> - <class '__main__.B'>
b.y return ---- @<2>@ 333
b.z return ---- @<3>@ 444
b.__dict__ return ---- @<4>@ b.__dict__ is {'z': 444}
A.__get__ <__main__.A object at 0x7eff430c8550> - None - <class '__main__.B'>
B.x return ---- @<5>@ 222
A.__get__ <__main__.A object at 0x7eff430c8668> - None - <class '__main__.B'>
B.y return ---- @<6>@ 333
__set__(self, instance, value)
方法可以拦截住通过实例设置实例自身的属性操作,转而调用数据描述器中的__set__
方法,将要设置的值注入到 self
(就是数据描述器的一个实例) 中, 而不是注入到B类的示例中。 如果我们要获取到注入的数据,就要通过__get__
方法来获取,__get__
方法到底返回什么是有我们自己决定的。
描述器中有了 __set__
方法,实例中的属性与当前类中描述器同名,该属性不会记录到实例的 __dict__
中。该数据如何处理需要在 A类中的 __set__
方法中进行设置
所有的b.x
就会访问描述器的__get__()
方法,代码中返回的self就是描述器实例
,它的实例字典中就保存着self
和data
属性,可以打印b.x.__dict__
就可以看到这些属性。
如何访问描述器示例的属性
访问描述器示例的属性,需要在描述器的 __get__
方法中返回 描述器示例本身
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
#
def __set__(self, instance, value):
print('A.__set__ {} - {} - {}'.format(self, instance, value))
self.data = value
class B:
x = A()
y = A()
def __init__(self):
print('B.init ~~~~~~~~~~~~')
# self.x = A()
self.x = 100
self.y = 200
# self.y = A()
b = B()
b.x = 222
b.y = 333
b.z = 444
print(" b.x return ---- @<1>@", b.x)
print(" b.y return ---- @<2>@", b.y)
print(" b.z return ---- @<3>@", b.z)
print(" b.__dict__ return ---- @<4>@ b.__dict__ is ", b.__dict__)
print(" B.x return ---- @<5>@", B.x)
print(" B.y return ---- @<6>@", B.y)
# print(" B.__dict__[x] return ---- @<4>@ B.__dict__[x] is ", B.__dict__['x'])
print(" b.x.a1 return ---- @<7>@", b.x.a1)
----------------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
A.init ~~~~~~~~~~~~
A.init ~~~~~~~~~~~~
B.init ~~~~~~~~~~~~
A.__set__ <__main__.A object at 0x7fc096a2c550> - <__main__.B object at 0x7fc096a2c6d8> - 100
A.__set__ <__main__.A object at 0x7fc096a2c6a0> - <__main__.B object at 0x7fc096a2c6d8> - 200
A.__set__ <__main__.A object at 0x7fc096a2c550> - <__main__.B object at 0x7fc096a2c6d8> - 222
A.__set__ <__main__.A object at 0x7fc096a2c6a0> - <__main__.B object at 0x7fc096a2c6d8> - 333
A.__get__ <__main__.A object at 0x7fc096a2c550> - <__main__.B object at 0x7fc096a2c6d8> - <class '__main__.B'>
b.x return ---- @<1>@ <__main__.A object at 0x7fc096a2c550>
A.__get__ <__main__.A object at 0x7fc096a2c6a0> - <__main__.B object at 0x7fc096a2c6d8> - <class '__main__.B'>
b.y return ---- @<2>@ <__main__.A object at 0x7fc096a2c6a0>
b.z return ---- @<3>@ 444
b.__dict__ return ---- @<4>@ b.__dict__ is {'z': 444}
A.__get__ <__main__.A object at 0x7fc096a2c550> - None - <class '__main__.B'>
B.x return ---- @<5>@ <__main__.A object at 0x7fc096a2c550>
A.__get__ <__main__.A object at 0x7fc096a2c6a0> - None - <class '__main__.B'>
B.y return ---- @<6>@ <__main__.A object at 0x7fc096a2c6a0>
A.__get__ <__main__.A object at 0x7fc096a2c550> - <__main__.B object at 0x7fc096a2c6d8> - <class '__main__.B'>
b.x.a1 return ---- @<7>@ a1
通过示例进行属性赋值和通过类进行类属性赋值的区别
尝试着增加下面的2行代码,看看字典的变化
b.x = 500,这是调用数据描述器的 `__set__` 方法,或调用非数据描述器的实例覆盖。
B.x = 600,赋值即定义,这是覆盖类属性。把描述器给替换了。
Python中的描述器 staticmethod()
classmethod()
property()
描述器在Python中应用非常广泛。
Python的方法(包括 staticmethod()
和 classmethod()
)都实现为非数据描述器。因此,实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他实例不同的行为。
property()
函数实现为一个数据描述器。因此,实例不能覆盖属性的行为。
通过下面的示例理解 staticmethod
classmethod
property
到底是哪种数据描述器
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()
# a.z = 10000 # AttributeError: can't set attribute
A.M = 666
print(1, a.__dict__)
print(2, 'A.M ----- {}'.format(A.__dict__['M']))
print(3, 'a.M ----- {}'.format(a.M))
print(4, 'A.foo() ----- {}'.format(A.foo))
print(5, 'a.foo() ----- {}'.format(a.foo))
print(6, 'A.bar() ----- {}'.format(A.bar))
print(7, 'a.bar() ----- {}'.format(a.bar))
--------------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
1 {'foo': 100, 'bar': 200}
2 A.M ----- 666
3 a.M ----- 666
4 A.foo() ----- <bound method A.foo of <class '__main__.A'>>
5 a.foo() ----- 100
6 A.bar() ----- <function A.bar at 0x7fbfd218c6a8>
7 a.bar() ----- 200
foo、bar都可以在实例中覆盖,但是z不可以。
描述器实践
1、实现StaticMethod装饰器
实现StaticMethod装饰器,完成staticmethod装饰器的功能
# 类staticmethod装饰器
class StaticMethod: # 怕冲突改名
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner):
return self.fn
class A:
def __init__(self):
pass
@StaticMethod
def fn(): # fn = StaticMethod(fn)
print('====== I am fn ======')
a = A()
A.fn()
a.fn()
------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
====== I am fn ======
====== I am fn ======
2、实现ClassMethod装饰器
实现ClassMethod装饰器,完成classmethod装饰器的功能
# 类classmethod装饰器
from functools import partial
class ClassMethod: # 怕冲突改名
def __init__(self, fn):
self.fn = fn
def __get__(self, instance, owner): # (ClassMethod(), A(), A) 通过等价方式看得更明白
return partial(self.fn, owner)
# return lambda : self.fn(owner) # 同样可以实现片函数固定参数的效果
class A:
def __init__(self):
# pass
@ClassMethod
def fn(cls): # fn = ClassMethod(fn) 通过等价方式看得更明白 ClassMethod(fn) 就是 ClassMethod 的一个实例
print('====== I am {} ======'.format(cls.__name__))
a = A()
A.fn()
a.fn()
print(A.fn)
print('=' * 40)
print(A.__dict__)
--------------------------------------------------------
/home/ming/.pyenv/versions/scm368/bin/python /data/test1/test3.py
<class '__main__.A'>
====== I am A ======
====== I am A ======
functools.partial(<function A.fn at 0x7f2935bda400>, <class '__main__.A'>)
========================================
{'__module__': '__main__', '__init__': <function A.__init__ at 0x7f2935bda378>, 'fn': <__main__.ClassMethod object at 0x7f29375af668>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
3、对实例的数据进行校验
class Person:
def __init__(self, name:str, age:int):
self.name = name
self.age = age
对上面的类的实例的属性name
、age
进行数据校验
思路
- 写函数,在
__init__
中先检查,如果不合格,直接抛异常- 装饰器,使用inspect模块完成
- 描述器
函数方式实现
方法 1
import inspect
def argscheck(arg, typ):
if type(arg) != typ:
raise Exception('invalid type {} -x-> {}'.format(arg, typ))
class Person:
def __init__(self, name:str, age:int):
argscheck(name, str)
argscheck(age, int)
self.name = name
self.age = age
# p1 = Person('dy', 23)
# p2 = Person('zy', '23')
p3 = Person(123, 345)
------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test10.py
Traceback (most recent call last):
File "D:/pyporject/test/test10.py", line 16, in <module>
p3 = Person(123, 345)
File "D:/pyporject/test/test10.py", line 9, in __init__
argscheck(name, str)
File "D:/pyporject/test/test10.py", line 5, in argscheck
raise Exception('invalid type {} -x-> {}'.format(arg, typ))
Exception: invalid type 123 -x-> <class 'str'>
方法2
class Person:
def __init__(self, name:str, age:int):
self.argscheck(name, str)
self.argscheck(age, int)
self.name = name
self.age = age
def argscheck(self, arg, typ):
if type(arg) != typ:
raise Exception('invalid type {} -x-> {}'.format(arg, typ))
p1 = Person('dy', 23)
# p2 = Person('zy', '23')
p3 = Person(123, 345)
print(p1.__dict__)
--------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test10.py
Traceback (most recent call last):
File "D:/pyporject/test/test10.py", line 17, in <module>
p3 = Person(123, 345)
File "D:/pyporject/test/test10.py", line 7, in __init__
self.argscheck(name, str)
File "D:/pyporject/test/test10.py", line 14, in argscheck
raise Exception('invalid type {} -x-> {}'.format(arg, typ))
Exception: invalid type 123 -x-> <class 'str'>
写法3
# 写函数检查
class Person:
def __init__(self, name:str, age:int):
self.argslst = [ (name, str), (age, int) ]
self.argscheck(self.argslst)
self.name = name
self.age = age
def argscheck(self, items):
for arg,typ in items:
if type(arg) != typ:
raise Exception('invalid type {} -x-> {}'.format(arg, typ))
p1 = Person('dy', 23)
# p2 = Person('zy', '23')
# p3 = Person(123, 345)
print(p1.__dict__)
这种方法耦合度太高。
装饰器的方式
import inspect
def argcheck(cls):
def warpper(*args, **kwargs):
sig = inspect.signature(cls)
parmas = sig.parameters
keys = list(parmas)
for i,v in enumerate(args):
if parmas[keys[i]].annotation == inspect._empty or isinstance(args[i], parmas[keys[i]].annotation):
continue
else:
raise Exception('--- Invalid type')
for k,v in kwargs.items():
if parmas[k].annotation == inspect._empty or isinstance(v, parmas[k].annotation):
continue
else:
raise Exception('+++ Invalid type')
return cls(*args, **kwargs)
return warpper
@argcheck
class Person: # Person = argcheck(Person)
def __init__(self, name, age):
self.name = name
self.age = age
# p1 = Person('dy', 23)
# p1 = Person('zy', '23')
# p1 = Person(123, 345)
p1 = Person(name='dy', age=23)
p1 = Person(name='dy', age='23')
# p1 = Person(name=123, age=456)
print(p1.__dict__)
描述器方式实现
属性写入时,要做类型检查,需要使用数据描述器,写入实例属性的时候做检查。
每一个属性都应该是这个属性描述器。
初级版本(写死了)
class Argcheck:
def __init__(self, arg_name, arg_typ):
self.arg_name = arg_name
self.arg_typ = arg_typ
def __get__(self, instance, owner): # (Argcheck(), Person(), Person)
if instance:
return instance.__dict__[self.arg_name] # 不能用 return getattr(instance, self.arg_name) 否则会有无限递归
return self # 这里应该返回 self 否则 Person.age 的结果是 None
def __set__(self, instance, value): # (Argcheck(), Person(), value)
if isinstance(value,self.arg_typ):
instance.__dict__[self.arg_name] = value # 不能用 setattr() 否则会有无限递归
else:
raise Exception('--- invalid parameter {}'.format(value))
class Person:
name = Argcheck('name', str) # Person = Argcheck()
age = Argcheck('age', int) # Person = Argcheck()
def __init__(self, name:str, age:int):
self.name = name
self.age = age
# p1 = Person('dy', 23)
# p1 = Person('zy', '23')
# p1 = Person(123, 345)
p1 = Person(name='dy', age=23)
# p1 = Person(name='dy', age='23')
# p1 = Person(name=123, age=456)
print(p1.__dict__)
print(p1.name)
print(p1.age)
print(Person.age)
----------------------------------------------
C:\python36\python.exe D:/pyporject/test/test10.py
{'name': 'dy', 'age': 23}
dy
23
None
Process finished with exit code 0
代码看似不错,但是有硬编码,能否Person中只要写 __init__()
方法就行了?如下
class Person:
def __init__(self, name:str, age:int):
self.name = name
self.age = age
上面代码,需要
- 注入
name
、age
类属性,且使用描述器 - 提取
__init__()
方法的形参名称和类型注解的类型
需要写段程序完成上述功能
对类使用inspect会有什么效果?
import inspect
sig = inspect.signature(Person)
params = sig.parameters
print(sig)
print(params)
------------------------------------------------
C:\python36\python.exe D:/pyporject/test/test10.py
(name:str, age:int)
OrderedDict([('name', <Parameter "name:str">), ('age', <Parameter "age:int">)])
升级版本(写活了)
import inspect
class Argcheck:
def __init__(self, arg_name, arg_typ):
self.arg_name = arg_name
self.arg_typ = arg_typ
def __get__(self, instance, owner): # (Argcheck(), Person(), Person)
if instance:
return instance.__dict__[self.arg_name] # 不能用 return getattr(instance, self.arg_name) 否则会有无限递归
return self # 这里是否返回 self 都是可以的
def __set__(self, instance, value): # (Argcheck(), Person(), value)
if isinstance(value, self.arg_typ):
instance.__dict__[self.arg_name] = value # 不能用 setattr() 否则会有无限递归
else:
raise Exception('--- invalid parameter {}'.format(value))
def typeinject(cls):
sig = inspect.signature(cls)
parmas = sig.parameters
for name, parma in parmas.items():
print('+++', name, parma)
if parma.annotation != inspect._empty:
setattr(cls, name, Argcheck(name, parma.annotation))
return cls
@typeinject
class Person: # Person = checkargs(Person)
def __init__(self, name:str, age:int):
self.name = name
self.age = age
p1 = Person('dy', 23)
print(p1.__dict__)
p2 = Person('zy', '23')
print(p2.__dict__)
# p1 = Person(123, 345)
# p1 = Person(name='dy', age=23)
# # p1 = Person(name='dy', age='23')
# # p1 = Person(name=123, age=456)
# print(p1.name)
# print(p1.age)
# print(Person.age)
# sig = inspect.signature(Person)
# params = sig.parameters
# print(sig)
# print(params)
# print(Person.__dict__)
-----------------------------------------------
C:\python36\python.exe D:/pyporject/test/test10.py
+++ name name:str
Traceback (most recent call last):
+++ age age:int
File "D:/pyporject/test/test10.py", line 39, in <module>
{'name': 'dy', 'age': 23}
p2 = Person('zy', '23')
File "D:/pyporject/test/test10.py", line 35, in __init__
self.age = age
File "D:/pyporject/test/test10.py", line 18, in __set__
raise Exception('--- invalid parameter {}'.format(value))
Exception: --- invalid parameter 23
类装饰器写法
可以把上面的函数装饰器改为类装饰器,如何写?
import inspect
class Argcheck:
def __init__(self, arg_name, arg_typ):
self.arg_name = arg_name
self.arg_typ = arg_typ
def __get__(self, instance, owner): # (Argcheck(), Person(), Person)
if instance:
return instance.__dict__[self.arg_name] # 不能用 return getattr(instance, self.arg_name) 否则会有无限递归
return self # 这里是否返回 self 都是可以的
def __set__(self, instance, value): # (Argcheck(), Person(), value)
if isinstance(value, self.arg_typ):
instance.__dict__[self.arg_name] = value # 不能用 setattr() 否则会有无限递归
else:
raise Exception('--- invalid parameter {}'.format(value))
class Typeinject:
def __init__(self, cls):
self.cls = cls
def __call__(self, *args, **kwargs):
sig = inspect.signature(self.cls)
parmas = sig.parameters
for name, parma in parmas.items():
print('+++', name, parma)
if parma.annotation != inspect._empty: # 注入类属性
setattr(self.cls, name, Argcheck(name, parma.annotation))
return self.cls(*args, **kwargs) # 新构建一个新的Person对象
@Typeinject
class Person: # Person = Typeinject(Person)
# 类属性,由装饰器注入
# name = TypeCheck('name', str) # 硬编码
# age = TypeCheck('age', int) # 不优雅
def __init__(self, name:str, age:int):
self.name = name
self.age = age
# p1 = Person('dy', 23)
# p1 = Person('zy', '23')
# p1 = Person(123, 345)
# print(p1.__dict__)
# print(Person.__dict__)
-----------------p1 = Person('dy', 23) 结果---------------------------
C:\python36\python.exe D:/pyporject/test/test10.py
+++ name name:str
+++ age age:int
{'name': 'dy', 'age': 23}
-----------------p1 = Person('zy', '23') 结果---------------------------
C:\python36\python.exe D:/pyporject/test/test10.py
+++ name name:str
Traceback (most recent call last):
+++ age age:int
File "D:/pyporject/test/test10.py", line 44, in <module>
p1 = Person('zy', '23')
File "D:/pyporject/test/test10.py", line 34, in __call__
return self.cls(*args, **kwargs)
File "D:/pyporject/test/test10.py", line 41, in __init__
self.age = age
File "D:/pyporject/test/test10.py", line 18, in __set__
raise Exception('--- invalid parameter {}'.format(value))
Exception: --- invalid parameter 23
-----------------p1 = Person(123, 345) 结果---------------------------
C:\python36\python.exe D:/pyporject/test/test10.py
+++ name name:str
Traceback (most recent call last):
File "D:/pyporject/test/test10.py", line 45, in <module>
p1 = Person(123, 345)
File "D:/pyporject/test/test10.py", line 34, in __call__
return self.cls(*args, **kwargs)
File "D:/pyporject/test/test10.py", line 40, in __init__
self.name = name
File "D:/pyporject/test/test10.py", line 18, in __set__
raise Exception('--- invalid parameter {}'.format(value))
Exception: --- invalid parameter 123
+++ age age:int
__slots__
(解决实例字典耗空间的问题)
A declaration inside a class that saves memory by pre-declaring space for instance attributes and eliminating instance dictionaries. Though popular, the technique is somewhat tricky to get right and is best reserved for rare cases where there are large numbers of instances in a memory-critical application
__slots__
的引出
都是字典惹的祸。
字典为了提升查询效率,必须用空间换时间。
一般来说一个实例,属性多一点,都存储在字典中便于查询,问题不大。
但是如果数百万个实例,那么字典占的总空间就有点大了。
Python提供了 __slots__
可以把实例的(不是类的)属性字典 __dict__
省了。
__slots__
是在类中定义的,只有一份,实例中再也没有属性字典了。- 以后找属性名直接通过在类中的
__slots__
中找。
在没有使用 __slots__
的时候,先来看看实例字典占用内存的默认大小
import sys
class A:
X = 1
def __init__(self):
self.y = 5
self.z = 6
def show(self):
print(self.X, self.y, self.z)
a = A()
print(A.__dict__) # ?
print(a.__dict__) # ?
# print(b.__dict__) # b 实例已经没有自己的属性字典了
print('==' * 30)
print('size of "{}" is {:<4} bytes'.format("A.__dict__",sys.getsizeof(A.__dict__)))
print('size of "{}" is {:<4} bytes'.format("a.__dict__",sys.getsizeof(a.__dict__)))
---------------------------------------------------------------
C:\python36\python.exe D:/pyporject/scm/web/test.py
{'__module__': '__main__', 'X': 1, '__init__': <function A.__init__ at 0x000001B8FB2357B8>, 'show': <function A.show at 0x000001B8FB235840>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
{'y': 5, 'z': 6}
============================================================
size of "A.__dict__" is 48 bytes #
size of "a.__dict__" is 112 bytes # 实例的字典占用内存比类的字典还要大
思考
上面2个字典,谁的字典是个问题?
实例多达百万个的时候,这么多存放实例属性的字典是个问题
通过 __slots__
去除实例的属性自典
class A:
__slots__ = ('x','y') # 可以是元组
# __slots__ = ['x','y'] # 也可以列表
# __slots__ = 'x','y' # 也可以这样的元组
# __slots__ = 'x' # 也可以单独的值
def __init__(self):
self.x = 5
self.y = 6
def show(self):
print(self.x, self.y)
a = A()
a.show()
#
print('A', A.__dict__)
# print('obj', a.__dict__) # 再也没有 实例字典了。
print(a.__slots__)
-----------------------------------------------------------
C:\python36\python.exe D:/pyporject/scm/web/test.py
5 6
A {'__module__': '__main__', '__slots__': ('x', 'y'), '__init__': <function A.__init__ at 0x000001C8DF2257B8>, 'show': <function A.show at 0x000001C8DF225840>, 'x': <member 'x' of 'A' objects>, 'y': <member 'y' of 'A' objects>, '__doc__': None}
('x', 'y')
__slots__
告诉解释器,实例的属性都叫什么,一般来说,既然要节约内存,最好还是使用元组比较好。- 一旦类提供了
__slots__
,就阻止实例产生__dict__
来保存实例的属性。
尝试为实例 a 动态增加属性 a.newx = 5
返回 AttributeError: 'A' object has no attribute 'newx'
说明实例不可以动态增加属性了
A.NEWX = 20
,这是可以的,因为这个是类的属性。
__slots__
的继承
__slots__
不影响子类实例,不会继承下去,除非子类里面自己也定义了 __slots__
。
class A:
__slots__ = ('x','y') # 元组
def __init__(self):
self.x = 5
self.y = 6
def show(self):
print('self.x = {}, self.y = {}'.format(self.x, self.y))
class B(A): # B类 继承 A类
pass
a = A()
a.show()
print(1, a.__slots__)
print(2, A.__dict__.keys())
print(3, 'B', B().__dict__.keys())
print(4, a.__slots__)
----------------------------------------------------
C:\python36\python.exe D:/pyporject/scm/web/test.py
self.x = 5, self.y = 6
1 ('x', 'y')
2 dict_keys(['__module__', '__slots__', '__init__', 'show', 'x', 'y', '__doc__'])
3 B dict_keys([]) # B类并没有将A类中的 __slots__ 继承过来
4 ('x', 'y')
__slots__
应用场景
如果内存容量较为紧张,实例的属性简单、固定且不用动态增加,而需要构建在百万级以上对象,使用 slots 是一个好的选择。