魔术方法

一、实例化

方法 意义
__new__ 实例化一个对象,该方法需要返回一个值,如果该值不是cls的实例,则不会调用__init__ 该方法永远都是静态方法
class A:
    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)
        return None
    def __init__(self,name):
        self.name = name
a = A(1,2)
print(a)
#输出
<class '__main__.A'>
(1, 2)
{}
None

__new__ 方法很少使用,即使创建了该方法,也会使用return super().__new__(cls) 基类obejct的__new__ 方法来创建实例并返回。

二、可视化

方法 意义
__str__ str()函数、format()函数、print()函数调用,需要返回对象的字符串表达,如果没有定义,就去调用__repr__ 方法返回字符串表达,如果__repr__ 也没有定义,就直接返回对象的内存地址信息。
__repr__ 内建函数repr() 对一个对象获取字符串表达。
调用__repr__ 方法返回字符串表达,如果__repr__ 也没有定义,就直接返回object的定义就是显示内存地址信息
__bytes__ bytes()函数调用,返回一个对象的bytes表达,即返回bytes对象
class A:
    def __init__(self,name,age=16):
        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)
    def __bytes__(self):
        import json
        return json.dumps(self.__dict__).encode()
print(A('tom')) # print函数使用__str__
print('{}'.format(A('tom')))
print([A('tom')]) # []使用__str__,但其内部使用__repr__
print([str(A('tom'))]) # []使用__str__,其中的元素使用str()函数也调用__str__
print(bytes(A('tom')))
#输出
str = tom--16
str = tom--16
[repr = tom__16]
['str = tom--16']
b'{"name": "tom", "age": 16}'

三、bool

方法 意义
__bool__ 内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。
没有定义__bool** ,就找__len__() 返回长度,非0为真。
如果
__len__()** 也没有定义,那么所有实例都返回真
class A:pass
a = A()
print(bool(A))
print(bool(a))

class B:
    def __bool__(self):
        return False
print(bool(B))
print(bool(B()))

if B():
    print('Real B instance')

class C:
    def __len__(self):
        return 1
print(bool(C))
print(bool(C()))


if C():
    print("Real C instance")
#输出
True
True
True
False
True
True
Real C instance

注意:__bool__ 等函数只会影响实例,不会影响类

四、运算符重载

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

运算符 特殊方法 含义
<,<=,==,>,>=,!= __lt__ ,__le__ ,__eq__ ,__gt__ ,__ge__ ,__ne__ 比较运算符
+,-,*,/,%,//, **,divmod __add__ ,__sub__ ,__mul__ , __truediv__ ,__mod__ ,__floordiv__ ,__pow__ ,__divmod__ 算数运算符,移位、位运算也有对应的方法
+=,-+,*=,/=,%=,//=,**= __iadd** ,__isub ,**__imul__ ,__itruediv__ ,__imod__ ,__ifloordiv__ ,__ipow__

实现自定义类的实例的大小比较(非常重要,排序时使用)

class A:
   pass
print(A() == A())  #可以
print(A() > A())   #不可以

class A:
    def __init__(self, name ,age =18):
        self.name = name
        self.age = age
    def __eq__(self, other):
        print('~~~~~eq')
        return self.name == other.name and self.age == other.age
    def __gt__(self, other):
        print("~~~~gt")
        return self.age > other.age
    def __ge__(self, other):
        print("~~~~ge")
        return self.age >= other.age

tom = A('tom')
jerry = A('jerry', 16)
print(tom == jerry, tom != jerry)
print(tom > jerry, tom < jerry)
print(tom >= jerry, tom <= jerry)

#输出
~~~~~eq
~~~~~eq
False True
~~~~gt
~~~~gt
True False
~~~~ge
~~~~ge
True False

__eq__ 等于可以推断不等于

__gt__ 大于可以推断小于

__ge__ 大于等于可以推断小于等于

也就是用三个方法就可以把所有比较解决了

实现两个学生成绩差

class A:
    def __init__(self, name ,score):
        self.name = name
        self.score = score
tom = A('tom',67)
jerry = A('jerry',62)
print(tom.score - jerry.score)
5

class A:
    def __init__(self, name ,score):
        self.name = name
        self.score = score
    def __sub__(self, other):
        print("~~~~~sub")
        return self.score - other.score
    def __isub__(self, other):
        print("~~~~~isub")
        self.score -= other.score
        return self
    def __str__(self):
        print("~~~~~str")
        return "<{}--{}>".format(self.name,self.score)
tom = A('tom',87)
jerry = A('jerry',62)

print(tom.score - jerry.score)
print(tom-jerry)

jerry -= tom
print(tom)
print(jerry)
#输出
25
~~~~~sub
25
~~~~~isub
~~~~~str
<tom--87>
~~~~~str
<jerry---25>

list的+和+=区别。tuple呢

五、容器相关方法
方法 意义
__len__ 内建函数len(),返回对象的长度(>=0的整数),如果把对象当做容器类型看,就如同list或dict。
bool()函数调用的时候,如果没有__bool__() 方法, 则会看__len__() 方法是否存在,存在返回非0为真
__iter__ 迭代容器时,调用,返回一个新的迭代器对象
__container__ in成员运算符,没有实现,就调用__iter__方法遍历
__getitem__ 实现sefl[key]访问,序列对象,key接受整数位索引,或者切片。对于set和dict,key位hashable。key不存在引发keyerror异常
__setitem__ 和__getitem__ 的访问类似,是设置值的方法
__missing__ 字典或其子类使用__getitem__() 调用时,key不存在执行该方法
class A(dict):
    def __missing__(self,key):
        print('Missing key :', key)
        return 0 
a = A()
print(a['k'])

应用

设计一个购物车,能够方便增加商品,能够方便的遍历

class Cart:
    def __init__(self):
        self.items = []
    def __len__(self):
        return len(self.items)
    def __str__(self):
        return str(self.items)
    def __iter__(self):
        return iter(self.items)
    def __missing__(self, key):
        print("missing :",key)
        return 0
    def __getitem__(self, item):
        return self.items[item]
    def __setitem__(self, key, value):
        self.items[key] = value
    def additem(self,*args):
        self.items.extend(args)
    def __contains__(self, item):
        print("~~~~~~~~~~~~~~~~",item)
        if item in self.items:
            return True
        else:
            return False
cart = Cart()
cart.additem('香蕉','电视',23,"haha")
print(cart)
cart[0] =  "葫芦波"
print(cart)
for n in cart:
    print(n)
print(len(cart),bool(cart))

print("haha" in  cart)
#输出
['香蕉', '电视', 23, 'haha']
['葫芦波', '电视', 23, 'haha']
葫芦波
电视
23
haha
4 True
~~~~~~~~~~~~~~~~ haha
True

#购物车

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

可调用对象

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

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

方法 意义
__call__ 类中定义一个该方法,实例就可以像函数一样调用

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

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,6)
print(p)
print(p())
#输出
<__main__.Point object at 0x7f9fa805bfa0>
<point 4:6>

#累加
class Adder:
    def __call__(self, *args):
        self.result = sum(args)
        return self.result
adder = Adder()
print(adder(*range(4,7)))
print(adder.result)
#输出
15
15

应用

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

增加迭代数列的方法、返回数列长度、支持索引查找数列项的方法。

class Fib:
    def __init__(self):
        self.items = [0,1,1]
    def __getitem__(self, item):
        print(item)
        if item < 0:
            raise IndexError('下标越界')
        if item >= len(self):
            for n in range(len(self.items),item+1):
                self.items.append(self.items[n-1] + self.items[n-2])
                print("数组:{}".format(self.items))
        return self.items[item]
    def __call__(self, index):
        return self[index]

    def __str__(self):
        return str(self.items)
    def __setitem__(self, key, value):
        self.items[key] = value
    def __iter__(self):
        return iter(self.items)
    def __len__(self):
        return len(self.items)


f = Fib()

print(f[9],f(4))
#输出
9
数组:[0, 1, 1, 2]
数组:[0, 1, 1, 2, 3]
数组:[0, 1, 1, 2, 3, 5]
数组:[0, 1, 1, 2, 3, 5, 8]
数组:[0, 1, 1, 2, 3, 5, 8, 13]
数组:[0, 1, 1, 2, 3, 5, 8, 13, 21]
数组:[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
4
34 3

上下文管理

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

with open('path') as f:
   pass

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

class Point:
    pass
with Point() as p:  #这里会报错
    pass
#报错信息
Traceback (most recent call last):
  File "/Users/dujie/PycharmProjects/t1/context.py", line 3, in <module>
    with Point() as p:
AttributeError: __enter__

提示属性错误,没有__enter__或__exit__属性

上下文管理对象

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

方法 意义
__enter__ 进入与此对象相关的上下文。如果存在该方法,with语法会吧该方法的返回值作为绑定到as子句中指定的变量上
__exit__ 退出与此对象相关的上下文

__enter__ return self. 的时候a1才是a的别名,a==a1 ,a is a1

__exit__ return 值如果等价为True,压制异常,不报异常

mport  time

class Point:
    def __init__(self):
        print('1,init~~~~~~')
        time.sleep(1)
    def __enter__(self):
        print('2,enter~~~~')
        time.sleep(1)
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('3,exit~~~~~')
        time.sleep(1)
with Point() as f:
    print('4,with start')
    time.sleep(1)
    print('5,with end')

#输出
1,init~~~~~~
2,enter~~~~
4,with start
5,with end
3,exit~~~~~

实例化对象的时候,并不会调用enter,进入with语句块调用__enter__方法,然后狗执行语句体,最后离开with语句块的时候,调用__exit__方法。

with可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。注意,with并不开启一个新的作用域

上下文管理的安全性

看看异常对上下文的影响

import  time

class Point:
    def __init__(self):
        print('1,init~~~~~~')
        time.sleep(1)
    def __enter__(self):
        print('2,enter~~~~')
        time.sleep(1)
    def __exit__(self, exc_type, exc_val, exc_tb):
        print('3,exit~~~~~')
        time.sleep(1)
with Point() as f:
    print('4,with start')
    raise Exception('error')
    time.sleep(1)
    print('5,with end')

#输出
1,init~~~~~~
2,enter~~~~
4,with start
3,exit~~~~~
Traceback (most recent call last):
  File "/Users/dujie/PycharmProjects/t1/context.py", line 15, in <module>
    raise Exception('error')
Exception: error

可以看到,在抛出异常的情况下,with的__exit__ 照样执行,上下文管理是安全的

with语句
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('111.py')
with f as p:
    print(f)
    print(p)
    print(f is p)  #打印True
    print(f == p)  #打印True

p = f = None

p = Point()
with p  as f :
    print("in with~~~~")  
    print(p == f)  #打印False,因为问题在与__enter__方法,这个方法会将返回值赋给f,因为上面enter 返回值为None所以这里是False
    print('with over')

修改上例

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~~~~~')
p = Point()
with p  as f :
    print("in with~~~~")
    print(p == f)   #此时就是True,因为enter 方法返回值为self,即是p,会将p赋给f,所以相等
    print('with over')

with语法,会调用with后的对象的__enter__方法,如果有as,则将该方法的返回值赋给as 子句的变量。

上例,可以等价为f = p.__enter__()

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

如何用支持上线文的类来对add函数计时

import  time
import datetime
def add(x, y):
    return  x + y
class Point:
    def __init__(self,fn):
        self.__fn = fn
        print('1,init~~~~~~')
    def __enter__(self):
        print('2,enter~~~~')
        self.start = datetime.datetime.now()
        return self.__fn
    def __exit__(self, exc_type, exc_val, exc_tb):
        print(exc_tb,exc_type,exc_val)
        print('3,exit~~~~~')
        result = (datetime.datetime.now() - self.start).total_seconds()
        print("tooks: {}s".format(result))
    def __call__(self, *args, **kwargs):
        return self.__fn(*args,**kwargs)

with Point(add) as f:
    print(f(4,6))
    print(add(4,7))

使用contextmanager实现上下文管理

from  contextlib import contextmanager
import datetime
import time


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

@contextmanager
def test(fn):
    print('开始前')
    start = datetime.datetime.now()
    try:
        yield fn
    finally:
        print('开始后')
        end = (datetime.datetime.now() - start).total_seconds()
        print('结束了 {}'.format(end))

with test(add) as f:
    print(f(4,6))

反射

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

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

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

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

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

内建函数 意义
getattr(object,name[,defalut]) 通过name返回object的属性值。当属性不存在时,将使用defalut返回,如果没有defalut,则抛出AttributeError。name必须为字符串
setattr(object,name,value) object的属性存在,则覆盖,不存在,新增
hasattr(object,name) 判断对象是否有这个名字的属性,name必须为字符串
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
p1 = Point(4,6)
print(p1.x ,p1.y)
4 6
print(getattr(p1, 'x'))
4
print(getattr(p1, 'y'))
6
print(getattr(p1, 'z',100))
100
setattr(p1, 'x', 1000)
print(p1.x)
1000
print(getattr(p1, 'x'))
1000
反射相关的魔术方法

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

__getattr__()
Text
当属性找不到的时候调用,返回值就是该属性的值,实例字典z?mro中从左到右所有类开始找
class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __getattr__(self, item):
        print('getattr')
        print(item)
        return 100


p1 = Point(4,6)
print(p1.x ,p1.y)
4 6
print(p1.g)
getattr
g
100

实例属性查找顺序为:

instance.__dict__ –> instance.__class__.__dict

__setattr__()

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __getattr__(self, item):
        print('getattr')
        print(item)
        return 100
    def __setattr__(self, key, value):
        print('setattr~~ {} = {}'.format(key,value))






p1 = Point(4,6)
print(p1.x ,p1.y)
print(p1.__dict__)
setattr~~ x = 4
setattr~~ y = 6
getattr
x
getattr
y
100 100
{}

p1 的实例字典里什么都没有,而且访问x和y属性的时候竟然访问到了__getattr__()

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __getattr__(self, item):
        print('getattr')
        print(item)
        return 100
    def __setattr__(self, key, value):
        print('setattr~~ {} = {}'.format(key,value))
        self.__dict__[key] = value





p1 = Point(4,6)
print(p1.x ,p1.y)
print(p1.__dict__)

#输出
setattr~~ x = 4
setattr~~ y = 6
4 6
{'x': 4, 'y': 6}

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

__delattr__()
class Point:
    z = 100
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __delattr__(self, item):
        print('delattr,{}'.format(item))
p1 = Point(4,6)
del p1.x
del  p1.z
print(p1.__dict__)
print(Point.__dict__)
del Point.z
print(Point.__dict__)

#输出
delattr,x
delattr,z
{'x': 4, 'y': 6}
{'__module__': '__main__', 'z': 100, '__init__': <function Point.__init__ at 0x7fee9811c820>, '__delattr__': <function Point.__delattr__ at 0x7fee9811c790>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}
{'__module__': '__main__', '__init__': <function Point.__init__ at 0x7fee9811c820>, '__delattr__': <function Point.__delattr__ at 0x7fee9811c790>, '__dict__': <attribute '__dict__' of 'Point' objects>, '__weakref__': <attribute '__weakref__' of 'Point' objects>, '__doc__': None}

Process finished with exit code 0

通过实例删除属性,就会尝试调用该魔术方法

__getattribute__

class Point:
  Z = 100
  def __init__(self, x, y):
    self.x = x
    self.y = y
p1 = Point(4, 5)
print(p1.x, p1.y)
print(Point.Z, p1.Z)
print('-' * 30)


# 为Point类增加__getattribute__,观察变化
class Point:
  Z = 100
  def __init__(self, x, y):
    self.x = x
    self.y = y
  def __getattribute__(self, item):
      print(item)
p1 = Point(4, 5)
print(p1.x, p1.y)
print(Point.Z, p1.Z)
print('-' * 30)
######输出
4 5
100 100
------------------------------
x
y
None None
Z
100 None
------------------------------

实例的所有属性访问,第一个都会调用__getattribute__ 方法,它阻止了属性的查找,该方法应该返回(计算后的)值或者抛出一个AttributeError异常。

  • 它的return值将作为属性查找的结果。
  • 如果抛出AttributeError异常,则会直接调用__getattr__ 方法,因为表示属性没有找到
class Point:
  Z = 100
  def __init__(self, x, y):
    self.x = x
    self.y = y
  def __getattr__(self, item):
      return 'missing {}'.format(item)
  def __getattribute__(self, item):
      print(item)
      #return object.__getattribute__(self,item)
     # return super().__getattribute__(item)

p1 = Point(4, 5)
print(p1.x, p1.y)
print(p1.ss)
print(Point.Z, p1.Z)
print(p1.__dict__)
print('-' * 30)
#输出
x
y
4 5
ss
missing ss
Z
100 100
__dict__
{'x': 4, 'y': 5}
------------------------------

__getattribute__ 方法中为了避免在该方法中无限的递归,它的实现应该永远调用基类的同名方法以访问需要的任何属性,例如object.__getattribute__(self,name)。或者super().__getattribute__(item)

一般不要使用它

总结:

魔术方法 意义
__getattr__() 当通过搜索实例、实例的类及祖先类查不到属性,就会调用此方法
__setattr__() 通过 . 访问实例属性,进行增加、修改都要调用它
__delattr__() 当通过实例来删除属性时调用此方法
__getattribute__ 实例所有的属性调用都从这个方法开始

实例属性查找顺序:

实例调用__getattribute__() ——> 实例字典(instance.__dict__) ——> 实例的类的类字典(instance.__class__.__dict__) ——>继承的祖先类(直到object)的__dict__ ——>调用__getattr__()

__hash__

方法 意义
__hash__ 内建函数hash()调用的返回值,返回一个整数。如果定义这个方法该类的实例就可hash
print(hash(1))
print(hash('tom'))
print(hash(('tom',)))
#输出
1
8137844429138158502
-7052210149325465434
class A:
    def __init__(self,name):
        self.name = name
    def __hash__(self):
        return 1
    def __repr__(self):
        return self.name
a1 = A('tom')
a2 = A('tom')
print(a1,a2,hash(a1),hash(a2))
#输出
tom tom 1 1
print([a1,a2])
print((a1,a2))
print({a1,a2})
print({a1,a1})
#输出
[tom, tom]
(tom, tom)
{tom, tom}
{tom}

#元组
t1 = ('tom',)
t2 = ('tom',)
print(t1 is t2)
print(t1 == t2)
print({t1,t2})
#输出
True
True
{('tom',)}

上例子中,A的实例放在set中,他们hash值是相同的,为什么不能去重?

hash值相同就会去重吗?

class A:
    def __init__(self,name):
        self.name = name
    def __hash__(self):
        return 1
    def __repr__(self):
        return self.name
    def __eq__(self, other):
        return self.name == other.name
a1 = A('tom')
a2 = A('tom')
print({a1,a2})
print({A('jerry'),A('jerry')})
方法 意义
__eq__ 对应==操作符,判断2个对象内容是否相等,返回bool值
定义了这个方法,如果不提供__hash__方法,那么实例将不可hash

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

hash值相等,只是hash冲突,不能说明两个对象是相等的

因此,一般来说实现__hash__方法,要同时实现__eq__方法。去重依赖__eq__方法

不可hash对象isinstancee(p1,collections.Hashable)一定为False

思考:

1. list类实例为什么不可hash?

源码中有一句 __hash__ = None,也就是如果调用 __hash__ ()相当于None(),一定报错。

所有类都继承object,而这个类是具有 __hash__ ()方法的,如果一个类不能被hash,就把

__hash__ 设置为None。

2. functools.lru_cache使用到的functools._HashedSeq类继承自list,为什么可hash?

_HashedSeq类提供了__hash__方法,这个方法实际上计算的是元组的hash值

练习

设计二维坐标类Point,使其成为可hash类型,并比较2个坐标的实例是否相等?

class Point:
    def __init__(self, x , y):
        self.x = x
        self.y = y
    def __hash__(self):
        return hash((self.x,self.y))
    def __eq__(self, other):
        return self.x == other.x and self.y == other.y
    def __repr__(self):
        return "<Point: {}:{}>".format(self.x,self.y)

a = Point(4,5)
b = Point(4,5)
print(a == b)
print(a is b )
print(hash(a))
print(hash(b))
print(a,b)
#输出
True
False
-1009709641759730766
-1009709641759730766
<Point: 4:5> <Point: 4:5>