抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

python魔术方法一

实例化

方法 意义
__new__ 实例化一个对象,该方法需要返回一个值,如果该值不是cls的实例,则不会调用__ini__,该方法永远都是一个静态方法
class A:
    # __new__ 有两个作用: 1. 实例化, 2.元类的构造过程
    def __new__(cls, *args, **kwargs): # 1 构建实例,从无到有
        print(cls)
        print(args)
        print(kwargs)
        # return object.__new__(cls)
        return super().__new__(cls)
    def __init__(self,x,y):
        print('init ~~~')
        self.x = x
        self.y = y 


t  = A(4,5) # 1. A.__new__(1) -->  object.__new__(A) --> A instance 2. instance.__init__(4,5)   # 实际的调用过程
print(t.x)

可视化

方法 意义
__str__ str()函数、format()函数、print()函数调用,需要返回对象的字符串表达。如果没有定义,就去调用__repr__方法返回字符串表达,如果__repr__没有定义,就直接返回对象的内在地址信息
__repr__ 内建函数repr()对一个对象获取字符串表达。调用__repr__方法返回字符串表达,如果__repr__也没有定义,就直接返回object的定义就是显示内存地址信息
__bytes__ bytes() 函数调用,返回一个对象的bytes表达,即返回bytes对象
class Point:
    def __init__(self,x,y):
        self.x = x 
        self.y = y 
    def __str__(self): # 重写了__str__方法,返回一个字符串,主要是为了在面向对象的时候,打印对象的时候,能够打印出有意义的信息
        return 'Point({},{})'.format(self.x,self.y)

t = Point(4,5)
print(type(t)) # <class '__main__.Point'>
print(t) # Point(4,5) 这个方法等价于 print(str(t))
print(str(t)) # Point(4,5)
class Point:
    def __init__(self,x,y):
        self.x = x 
        self.y = y 
    def __str__(self): # 重写了__str__方法,返回一个字符串,主要是为了在面向对象的时候,打印对象的时候,能够打印出有意义的信息
        return 'Point({},{})'.format(self.x,self.y)

    def __repr__(self):
        return 'Point({},{})'.format(self.x,self.y)


t = Point(4,5)
print(type(t)) # <class '__main__.Point'>
print(t) # Point(4,5) 这个方法等价于 print(str(t))
print(str(t)) # Point(4,5)
print([t,str(t)],"{}".format(t))  # 到这里是没有_reper_方法的,所以会调用__str__方法 ,打印结果 [<__main__.Point object at 0x102b778e0>, 'Point(4,5)']
print(repr(t), [t, str(t)]) #

bool

方法 意义
__bool__ 内建函数bool(),或者对象放在逻辑表达式的位置,调用这个函数返回布尔值。没有定义__bool__(),就找__len__()返回长度、非0为真。如果__len__()也没有定义,那么所有实例都返回真
class B:
    def __bool__(self):
        # return False
        # return True
        return bool(0)
        # return bool(1)
print(bool(B())) # False
class A: pass 
print(bool(A())) # True 一个实例的对象,默认是True
class B:
    def __len__(self): # 容器类的实例,用长度来判断True or False
        return 0
        # return 1
print(bool(B())) # False
class B:
    def __bool__(self):
        return bool(len(self)) # 实际上等价于 len(self) => self.__len__()
        # 再者bool方法和len方法同时存在,那么bool方法会优先调用
    def __len__(self): # 容器类的实例,用长度来判断True or False
        return 0
        # return 1

print(bool(B())) # False
class B:
    def __bool__(self):
        print('bool...')
        return False
    def __len__(self):
        print('len...')
        return 100

t = B()
print(bool(t)) # False bool优先调用bool方法,如果没有bool方法,再调用len方法,len方法也没有,恒为True
print(len(t)) # len优先调用len方法

运算符重载

  • operator模块提供以下的特殊方法,可以将类的实例使用下面的操作符来操作
运算符 特殊方法 含义
<, <=, ==, >, >=, != __lt__, __le__, __eq__, __gt__, __ge__, __ne__ 比较运算符
*, /, %, **, divmod __mul__, __truediv__, __mod__, __pow__, __divmod__ 算数运算符
+=, -=, *=, /=, %=, **= __iadd__, __isub__, __imul__, __itruediv__, __imod__, __ifloordiv__, __ipow__ 赋值运算符
class Student:
    def __init__(self,name,age=20):
        self.name = name
        self.age = age

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

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

    def __lt__(self,other):
        return self.age < other.age

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

    def __str__(self):
        print('-' * 30)
        return "<Student {} {}>".format(self.name,self.age)

t1 = Student('Tom',22)
t2 = Student('Jerry')
print(t1 > t2) # True # t1.__gt__(t2)
print(t1 < t2) # False # t1.__lt__(t2)
print(t1 >= t2) # True # t1.__ge__(t2)
print(t1 <= t2) # False # t1.__le__(t2)
print(t2 >= t1) # False # t2.__ge__(t1)
t3 = Student('Tom',22)
print(t1 == t3) # True # t1.__eq__(t3)
# 计算两同学的分数差

class Student:
    def __init__(self,name,score):
        self.name = name
        self.score = score

    def __repr__(self):
        return "<Student {} {}>".format(self.name,self.score)
    def __sub__(self,other):
        print(self)
        print(other)
        return self.score - other.score

    def __isub__(self,other):
        print('isub...')
        self.score -= other.score
        return self

t1 = Student('Tom',100)
t2 = Student('Jerry',90)
print(t1 - t2) # 这里用的是__sub__方法
tom = Student('Tom',100)
jerry = Student('Jerry',90)

tom -= jerry # <Student Tom 10> # 这里用的是__isub__方法
print(type(tom))
print(tom)
class Student:
    def __init__(self,name,score):
        self.name = name
        self.score = score

    def __repr__(self):
        return "<Student {} {}>".format(self.name,self.score)
    def __sub__(self,other):
        print(self)
        print(other)
        return self.score - other.score

    def __isub__(self,other):
        print('isub...')
        self.score -= other.score
        return self

t1 = Student('Tom',100)
t2 = Student('Jerry',90)
print(t1 - t2) # 这里用的是__sub__方法
tom = Student('Tom',100)
jerry = Student('Jerry',90)

tom -= jerry # <Student Tom 10> # 这里用的是__isub__方法
print(type(tom))
print(tom)

# 思考: list 的+ 和 += 有什么区别
# list 的+ 是生成一个新的list
# list 的+= 是在原来的list上进行操作,即就地覆盖
# 思考: tuple 的+ 和 += 有什么区别
# t1 + t2 => t3
# t1 += t2 => t1 = t1 + t2 => t1 = t4

容器相关的魔术方法

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

t = A(a=1,b='abc') 
print(t,type(t),isinstance(t,dict)) # {'a': 1, 'b': 'abc'} <class '__main__.A'>  True 
print(t['k'])  # k 0
class Cart:
    def __init__(self):
        self.__items = []

    def additem(self,item):
        self.__items.append(item) 
        return self
        # return self + item

    def __len__(self):
        return len(self.__items) 

    def __repr__(self):
        return str(self.__items)

    def __iter__(self): # 实例是可迭代对象
        #return iter(self.__items)
        yield from self.__items

    def __getitem__(self,index):
        print(index, '+++')
        return self.__items[index]

    def __setitem__(self,index,value):
        self.__items[index] = value

    def __add__(self,other):
        # self.__items.append(other)
        return self.additem(other)


cart = Cart()
cart.additem('apple')
cart.additem('banana')
cart.additem(3)
print(len(cart)) # 3
print(cart) # ['apple', 'banana', 3]

for i in cart:
    print(i)
# apple
# banana
# 3

print(3 in cart) # True
print(2 in cart) # False   
print(cart[1]) # banana
cart[1] = 'banana2' 
print(cart) # ['apple', 'banana2', 3]

cart.additem('orange').additem('pear')
print(cart) # ['apple', 'banana2', 3, 'orange', 'pear']

print(cart + 300 + 200 ) # ['apple', 'banana2', 3, 'orange', 'pear', 300, 200]
方法 意义
__call__ 类中定义一个该方法,实例 就可以像函数一样使用
  • 以下是实现斐波那契数列的类
class Fib:
    def __init__(self):
        self.items = [0,1,1]

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

    def __len__(self):
        return len(self.items)

    def __iter__(self):
        return iter(self.items)

    def __getitem__(self,index):
        if index < 0:
            raise IndexError("Index out of range")

        for i in range(len(self.items),index+1):
            self.items.append(self.items[i-1] + self.items[i-2])

        return self.items[index]

    # def __call__(self,index):
    #     return self[index]
    # __call__ = __getitem__
    # def __call__(self,index): # f(3) ==> f.__call__(3)
    # return self[index] # req  turn self.__getitem__(index)
    __call__ = __getitem__

f = Fib()
print(f[10]) # 55
print(str(f)) # [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
for i in f:
    print(i)
print(len(f))
print('---')
print(f[11]) # 89

print(f(11)) # 89

上下文管理

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

方法 意义
__enter__ 进入此对象下关的上下文。如果存在该方法,with语法会把该方法的返回值作为绑定到as子句中指定的变量上
__exit__ 退出与此对象的上下文
with open('test') as f: # open的作用是打开文件,返回一个文件对象,with是对文件对象的一个上下文管理
    pass

那么对with执行顺序的描述
AOP 面向切面编程

class A:
    def __init__(self):
        print(1,'init start ----')

    def __enter__(self):
        print(2,'enter start ----')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(3,'exit start ----')

with A():
    print('4, with start ----')
    import time
    time.sleep(3)
    print('5, with end ----')
# 1 init start ----
# 2 enter start ----
# 4, with start ----
# 5, with end ----
# 3 exit start ----
class A:
    def __init__(self):
        print(1,'init start ----')

    def __enter__(self):
        return self
        print(2,'enter start ----')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(3,'exit start ----')

t1 = A()

with t1 as t2: # t2 = t1.__enter__() --> t2 = t1
    print(t1,"   ", t2)
    print( t1 ==  t2)
    print( t1 is t2) 
import time 
import datetime
def add(x ,y):
    time.sleep(3)
    return x +y 

class A:
    def __init__(self,fn):
        print(1,'init start ----')
        self.__fn = fn

    def __enter__(self):
        print(2,'enter start ----')
        self.start = datetime.datetime.now()
        return self.__fn

    def __exit__(self, exc_type, exc_val, exc_tb):
        print(3,'exit start ----')
        delta = (datetime.datetime.now() - self.start).total_seconds()
        print('{} took {}s'.format(self.__fn.__name__,delta))

with A(add) as t:
    print(t(100,200))
  • 上下文应用场
  1. 增强功能
    1. 在代码执行的前后增加代码,以增强其功能。类似装饰器的功能。
  2. 资源管理
    1. 打开了资源需要关闭,例如文件对象、网络连接、数据库连接等。
  3. 权限验证
    1. 在执行代码之前,做权限的验证,在__enter__中处理
from contextlib import contextmanager
import time 
import datetime 

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

@contextmanager
def timeit(fn):
    print('之前的调用')
    start = datetime.datetime.now()
    try:
        yield fn 
    finally:
        print('之后的调用')
        delta = (datetime.datetime.now() - start).total_seconds()
        print('{} took {}s'.format(fn.__name__,delta))

with timeit(add) as t:
    print(t(100,200))
# 之前的调用
# 300
# 之后的调用
# add took 3.000754s

利用简单的yeild一次的生成器管理

评论