码迷,mamicode.com
首页 > 其他好文 > 详细

pyhton中的魔术方法

时间:2019-05-06 20:51:07      阅读:240      评论:0      收藏:0      [点我收藏+]

标签:组织   table   试验   相等   cloc   space   反转   管理方法   元素操作   

魔术方法 *****

特殊属性

属性 说明
__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 返回的就结果

注意
不能通过判断是否带引号来判断输出值的类型,类型判断要使用 typeisinstance

运算符重载 相关的魔术方法

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 ,异常的追踪信息
  • __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

装饰器实现计时功能

  1. 最简单的装饰器
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

上下文实现计时功能

  1. 通过自定义的上下文管理类的实例实现装饰器
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

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

  1. 将类的实例变成可调用对象来实现
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

关于解决文档字符串拷贝的回顾

  1. 上面的例子中国我们已经用到了文档字符串拷贝的几种解决方案
  2. 其实就是我们自己手动实现和利用python的functions模块中提供的update_warpper,或wraps实现
  3. 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

上面的类即可以用在上下文管理,又可以用做装饰器, 不足之处就是还有点重复计时。

上下文应用场景 ***

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

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发生处为生成器函数增加了上下文管理。这是为函数增加上下文机制的方式。

  1. 把yield之前的当做__enter__方法执行
  2. 把yield之后的当做__exit__方法执行
  3. 把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__ 方法方便。

反射

反射的概述

运行时,区别于编译时,指的是程序被加载到内存中执行的时候。
反射re?ection,指的是运行时获取类型定义信息。
一个对象能够在运行时,像照镜子一样,反射出其类型信息。

简单说,在Python中,能够通过一个对象,找出其typeclassattributemethod的能力,称为反射或者自省

具有反射能力的函数有 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 指代当前实例,调用者
instanceowner 的实例
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

可以看出执行的先后顺序吧?

  1. 类加载的时候,类变量需要先生成,而类B的x属性是类A的实例,所以类A先初始化,所以打印A.init。
  2. 然后执行到打印B.x.a1。
  3. 然后实例化并初始化B的实例b。
  4. 打印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就是一个描述器,类Bx属性是类A(描述器)的实例,那么通过类B自身或者类B的实例对的类Bx属性读取,成为对类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
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.a1b.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就是一个数据描述器,类Bx属性是类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就是描述器实例,它的实例字典中就保存着selfdata属性,可以打印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

对上面的类的实例的属性nameage进行数据校验

思路

  1. 写函数,在 __init__ 中先检查,如果不合格,直接抛异常
  2. 装饰器,使用inspect模块完成
  3. 描述器
函数方式实现

方法 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

上面代码,需要

  • 注入nameage 类属性,且使用描述器
  • 提取 __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 是一个好的选择。

pyhton中的魔术方法

标签:组织   table   试验   相等   cloc   space   反转   管理方法   元素操作   

原文地址:https://www.cnblogs.com/shichangming/p/10821857.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!