小言_互联网的博客

python学习之特殊属性和魔术方法

320人阅读  评论(0)

https://blog.51cto.com/11233559/2422351
1 总述

属性 含义
__name__ 类函数,方法等的名字
__module__ 类定义所现在的模块名
__class__ 对象或类所属的类
__bases__ 类的基类的元素,顺序为他们在基类列表中出现的顺序
__doc__ 类/函数的文档字符传,如果没有定义则为None
__mro__ 类的mro,class.mro()返回
__dict__ 类或实例的属性,可写的字典
__dir__ 返回了类或者对象所有成员列表,dir()函数调用的是_dir_(),如果提供了_dir_(),则返回属性列表,否则尽可能从__dict__属性中收集信息

name

获取类和函数的名字

class  A:
    pass
class  B(A):
    pass
def  C():
    pass
print (A.__name__,B.__name__,C.__name__,sep='\n')

module

类定义所在的模块名

#newtest.py
class  A:
    pass
class B:
    pass
print  (A.__module__,B.__module__)

#test1.py
import sys
n = __file__.split(r"/")
sys.path.append("/".join(n[:-2]))
import newtest
print(newtest.A.__module__)

class

对象或类所属的类

class  A:
    pass
class B(A):
    pass
a=A()
b=B()
print (A.__class__,B.__class__,sep='\n')  #类所属的类是class
print (a.__class__,b.__class__,sep='\n')  # 对象所属的类是实实在在的类

bases

类的基类的元组,顺序是他们在基类列表中出现的顺序

class  A:
    pass
class B(A):
    pass
class C(B):
    pass
class E:
    pass
class D(E,C):
    pass
print (A.__bases__,B.__bases__,C.__bases__,D.__bases__,sep='\n')

DOC

文档字符串,针对类和函数有效,若不存在,则返回为None

class  A:
    '''this  is  class'''
    pass
def B():
    '''this is  function'''
    pass
class C:
    pass
print (A.__doc__,B.__doc__,C.__doc__,sep='\n')

mro

类的mro。返回多继承中的查找顺序

class  A:
    pass
class B(A):
    pass
class C(B):
    pass
class E:
    pass
class D(E,C):
    pass
print (A.__mro__,B.__mro__,C.__mro__,D.__mro__,sep='\n')

dict

类或者实例的属性,可写的字典

class  A:
    a=10
    def  __init__(self,x):
        self.x=5
a=A(3)

print (A.__dict__)
print (a.__dict__)

dir

dir 返回了类或者对象所有成员名称列表,dir()函数调用的是_dir_(),如果提供了_dir_() ,则返回属性的列表,否则会尽量从__dict__属性中收集

dir() 对于不同类型的对象具有不同的行为:
1 如果对象是模块对象,则列表包含模块的属性名

import  re
def  foo(x):
    y=1
print (dir())  # 输出当前模块信息,此处会打印当前导入的模块和导入的函数
print ('='*20)
print (dir(re))
print ('='*20)
print (dir(foo))


2 如果对象是类型或者类对象,列表包含类的属性名,以及其他基类的属性名

class A:
    a='1234'
    def __init__(self):
        pass
class  B(A): # 此处调用父类,其dir中会包含父类的属性
    pass
print (dir())  # 输出当前模块信息,此处会打印当前导入的模块和导入的函数
print ('='*20)
print (dir(A),dir(B),sep="\n"+'='*20+"\n")  # 此中DIR属性父类和子类是完全相同的,但dict中却是不同的
print ('='*20)
print (A.__dict__,B.__dict__,sep="\n"+'='*20+"\n")


3 如果是对象,列表包含对象的属性名,它的类的属性名和基类的属性名

class A:
    a='1234'
    def __init__(self):
        self.x=10
class  B(A): # 此处调用父类,其dir中会包含父类的属性
    pass
a=A()
print (dir())  # 输出当前模块信息,此处会打印当前导入的模块和导入的函数
print ('*'*40)
print (dir(A),dir(B),dir(a),sep="\n"+'='*20+"\n") #此处若是打印实例的属性,则会吧类的属性也打印上来


4此处对属性名进行了重写操作

class A:
    a='1234'
    def __init__(self):
        self.x=10
class  B(A): # 此处调用父类,其dir中会包含父类的属性
    def __dir__(self): # 此处是针对实例设置的,和类本身并无关系
        return ['this is class B '] # 此处是dir返回是列表,若使用字符串,则会处理成列表进行返回
a=A()
b=B()
print (dir())  # 输出当前模块信息,此处会打印当前导入的模块和导入的函数,以及实例后的对象
print ('*'*40)
print (dir(A),dir(B),dir(a),dir(b),sep="\n"+'='*20+"\n") #此处若是打印实例的属性,则会吧类的属性也打印上来

__slots__槽位
1 问题引出
都是字典惹的祸
字典为了提升查询效率,必须用空间换时间
一般来说一个对象,属性多一点,都存储在字典中便于查询,问题不大,但是数百万个对象,那么字典就占得有点大了,这个时候,python便提供了__slots__

魔术方法

描述 方法
初始化和销毁 __init__和__del__
在字典和set中使用 __hash__
布尔类型,常用于判断语句 __bool__
可视化,用于输出对应的类型 __str__和__repr__
运算符重载 __eq__,__ne__,__gt__,__lt__等
容器和大小相关和操作相关属性 __getitem__,__setitem__等
可调用对象,将实例化的对象当成一个函数去调用,一旦可以当函数调用 __call__
上下文管理(with open(x) as f 等形式 __enter__,__exit__
反射 __getattr__, __setattr__,__delattr__
描述器 Object.__get__(self,instance,owner)
Object.__set__(self,instance,value)
Object.delete(self,instance)

初始化和销毁

class X:
    def __init__(self,name):
        self.name=name
        self.x=10
        print ("init  instance")
    def __del__(self):
        print ('delete {}'.format(self.name))
a=X('a')
del  a 

hash
1 简介

hash 中最基础的hash就是取模运算。
list 不能hash的原因
list 源码: 其中hash=None,在调用None的时候自然是不能hash的

bool

_bool_ 内建函数bool(), 或者对象放在逻辑表达式的位置,调用这个函数返回布尔值,没有定义_bool_,就找_len_ 返回长度,非0为真,如果__len__也没有定义,则所有的实例都返回是真。

class Point:  # 此类未定义len和bool,因此其返回值为恒真
    def  __init__(self):
        self.x=3
        self.y=4
    # def __bool__(self):
    #     return False

print (bool(Point()))

class Point:
    def  __init__(self):
        self.x=3
        self.y=4
    def __bool__(self): # 此处定义了bool的返回值为False,则调用bool()返回结果应该为False
        return False
print (bool(Point()))

class Point:
    def  __init__(self):
        self.x=3
        self.y=4
    # def __bool__(self): # 此处定义了bool的返回值为False,则调用bool()返回结果应该为False
    #     return False
    def __len__(self): # 此处用于当bool不存在时的找寻位置,为0则表示为空,则为False
        return 0
print (bool(Point()))

class Point:
    def  __init__(self):
        self.x=3
        self.y=4
    def __bool__(self): # 同时存在,则以bool为准 
        return False
    def __len__(self): 
        return 1
print (bool(Point()))

这也就是为啥空的字典和空集合以及空列表为False的原因了,因为其没有定义bool,因此其只能通过访问len来实现了 。

可视化

1 简介

方法 意义
_repr_ 内建函数repr()对一个对象获取字符串表达式,如果一个类定义了_repr__但没有定义_str_,
那么在请求该类的实例的"非正式"的字符串也将调用_repr_()
_str_ str() 函数,内建函数format,print()函数调用,需要返回对象的字符串表达式
_bytes_ bytes 的时候,返回一个独享的bytes表达,及返回bytes对象
class Point:
    def  __init__(self):
        self.x=3
        self.y=4
    def  __repr__(self):
        return str([self.x,self.y])  #此处的返回必须使用字符串进行包裹,否则会报错
print (Point())

class Point:
    def  __init__(self):
        self.x=3
        self.y=4
    def  __repr__(self):
        return str([self.x,self.y])  #此处的返回必须使用字符串进行包裹,否则会报错
    def  __str__(self):  # 若存在此属性,则上述的表达式将不会被调用
        return  'abcdefg'
print (Point())

class Point:
    def  __init__(self):
        self.x=3
        self.y=4
    def  __repr__(self):
        return str([self.x,self.y])  #此处的返回必须使用字符串进行包裹,否则会报错
    def  __str__(self):  # 若存在此属性,则上述的表达式将不会被调用
        return  'abcdefg'
print (Point())
p1=Point()
p2=Point()
lst=[p1,p2]
for x in  lst:
    print (x)
print (lst)

class Point:
    def  __init__(self):
        self.x=3
        self.y=4
    def  __repr__(self):
        return str([self.x,self.y])  #此处的返回必须使用字符串进行包裹,否则会报错
    def  __str__(self):  # 若存在此属性,则上述的表达式将不会被调用
        return  'abcdefg'
print (Point())
p1=Point()
p2=Point()
lst=(p1,p2)
for x in  lst:
    print (x)
print (lst)
print (*lst)  #进行解包处理,此时是针对于对象上的,此时应该调用的是str

上述实例证明,当str和repr同时存在时,如果输出结果直接作用于对象上,则调用str方法,否则将调用repr方法

运算符重载

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:
    def  __init__(self,x):
        self.x=x
    def __lt__(self, other):
        return  self.x  < other.x
    def __eq__(self, other):
        return  self.x == other.x
    def __ne__(self, other):
        return  self.x  != other.x
    def __sub__(self, other):
        return  self.x - other.x

print (A(10)<A(5))
print (A(10)==A(5))
print (A(10) != A(5))
print (A(10)-A(5))

运算符重载中的反向方法

class Add:
    def __init__(self,x:int):
        self.x=x
    def __add__(self, other):
        print ('add',self)
        return self.x+other.x
    def __iadd__(self, other):
        print ('iadd',self)
        return  self.x+other.x
    def __radd__(self, other):
        print ('radd',self)
        return  self.x+other.x
class B:
    def __init__(self,x):
        self.x=x
a=Add(3)
b=B(4)
print (a+b)
print (b+a) 

运算符重载的应用场景
往往是面向对象实现的类,需要做大量的运算,而运算符是这种运算在数学上最常见的表达方式,int 类中,几乎实现了所有操作符,可以作为参考

容器相关方法

内建方法 含义
_len_ 内建函数len(),返回对象的长度(>=0的整数),其实即使吧对象当作容器类型来看,
就如同list或dict,bool()函数调用的时候,如果没有_bool_()方法,
则会看_len_()方法是否存在,存在返回非0为真,第三方库中可能存在size,其和len的含义相同
_iter_ 迭代器时,调用,返回一个新的迭代器对象
_contains_ in成员运算符,没有实现,就调用__iter__方法遍历
_getitem_ 实现self[key]访问,序列对象,key接受整数为索引,或者切片,对于set和dict,key为hashable,key不存在时引KeyError异常
_setitem_ 和__getitem__的访问相似,是设置值的方法
_missing_ 字典使用_getitem_()调用时,key不存在执行该方法
class  Item:
    def __init__(self,name,*args):
        self.name=name
        self.lst=list(args)
    def  __len__(self):
        return len(self.lst)
    def __iter__(self):
        return iter(self.lst)  # 此处返回是一个迭代器,必须是一个迭代器
    def __add__(self, other):  # 此处使用+ 号返回一个列表
        self.lst.append(other)
        return self
    def __getitem__(self, index):  # 此处应用于列表时,表示为索引,此处应用于字典时,表示key
        if  index   > len(self.lst):
            print ('Key Error')
        else:
            return self.lst[index]
    def __setitem__(self, index, value): # 此处表示修改属性列表中的值
        if  index   > len(self.lst):
            print ('Key Error')
        else:
            self.lst[index]=value
            return   self
    # def __missing__(self, key): # 此方法只能适用于字典的处理
    #     pass
    def  __repr__(self):
        return str(self.lst)  # 此处对其进行可视化处理
a=Item('mysql',12,3,45,678,8909)
print (len(a))
# 此处调用了__iter__方法
for i  in a:
    print (i)
print ('++++++++++++++++')
print (a[2])  # 此处调用了__getitem__方法,用于获取值
a+10  # 此处使用__add__方法进行加入,此处追加到列表的末尾
print (a[-1])  # 获取列表的最后一个元素,则得到此值
a[1]=20  # 使用__setitem__方法修改属性
print (a[1])  #返回对应位置的值
a+10+20+30+40  # 此处进行连加操作,因为其add方法返回是self,因此每次赋值后都会增加
print (a)

可调用对象

在python中一切皆对象,函数也不例外
可调用对象
方法
__call__类中出现该方法,实例就可以像函数一样调用,
可调用对象: 定义一个类,并实例化得到其实例,将实例像函数一样调用。调用是实例的,不是类的。

def  foo():
    print (foo.__module__,foo.__name__)

foo.__call__()# 此处的方法和下面的相同,皆是调用该函数
foo()
print (dir(foo))


函数的可调用原因是函数实现了\call()方法

def  foo():
    print (foo.__module__,foo.__name__)

print (foo.__call__) # 此处返回一个函数对象是一个wrapper
foo.__call__()# 此处的方法和下面的相同,皆是调用该函数
foo()

class  A:
    def __init__(self):
        self.x=1
    def __call__(self, *args):  # 此处的第一个是self,表明其是给实例使用的,并不是给类使用的
        return   args  # 此处返回一个元组

print (A()(12344))  # 此处第一个括号是实例化,第二个是传递参数并调用实例

练习

利用封装完成斐波那契额数列

方法1

class  A:
    def  __call__(self,num):
        a,b=0,1
        for  i in  range(num):
            print (b)
            a,b=b,a+b
A()(10)

方法2

class  A:
    def __init__(self):
        self.lst=[1,1,2]
    def  __call__(self,num):
        if  num < 3:
            return   self.lst[:num]
        else:
            for  i in range(num-3):
                self.lst.append(self.lst[-1]+self.lst[-2])
            return  self.lst
print (A()(10))

方法三

class  A:
    def __init__(self):
        self.lst=[1,1,2]
    def  __len__(self):
        return len(self.lst)
    def  __call__(self,x):
        if  len(self.lst)  >  x:
            return  self.lst[:x]
        for  i  in   range(2,x):
            self.lst.append(self.lst[i]+self.lst[i-1])
        return  self.lst
    def __getitem__(self, item):
        if item < 0:
            return  None
        if  len(self) > item:
            return self.lst[item]
    def __iter__(self):
        return  iter(self.lst)

a=A()
print (a(10))
print (a[4])
for x in a:
    print (x)

上下文管理

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

class A:
    pass

with  A()  as  f:
    pass

提示需要添加 __enter__属性
添加如下

class A:
    def  __enter__(self):
        pass

with  A()  as  f:
    pass

提示需要添加 __exit__属性

class A:
    def  __enter__(self):
        pass
    def  __exit__(self, exc_type, exc_val, exc_tb):
        pass
with  A()  as  f:
    pass

class A:
    def  __enter__(self):
        print ('__enter__')
    def  __exit__(self, exc_type, exc_val, exc_tb):
        print ('__exit__')
with  A()  as  f:
    pass


由此图可知,其调用顺序是先调用_enter_,后调用_exit_

属性

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

exit 中变量的含义:
1 exc_type: 异常类型,如果没有异常,则返回是None
2 exc_tb:异常追踪信息,如果没有异常,则是None
3 exc_va :异常对应的值,如果没异常,则是None
此处的return 用于压制异常,若此处是False,则会抛出异常,等效True 或 False
缺少了enter 进不去,缺少了exitc出不来

class A:
    def  __init__(self):
        print ('init instance')
    def  __enter__(self):
        print ('__enter__')
        return 1
    def  __exit__(self, exc_type, exc_val, exc_tb):
        print ('__exit__')
p=A()
with  p  as  f:  # 此处的p是__enter__的返回值,是f的参数,若此处__enter__无return,则默认返回为None,无意义
    print (p==f) # 此处用于比较p和f的关系
    print (p is f)
    print (p)
    print (f)


上述结论如下:
实例化对象的时候,并不会调用enter,进入with语句块会调用__enter__方法,然后执行语句体,最后离开with语句块的时候,调用__exit__方法
with 可以开启一个上下文运行环境,在执行前做一些准备工作,执行后做一些收尾工作。

class A:
    def  __init__(self):
        print ('init instance')
    def  __enter__(self):
        print ('__enter__')
        return 1
    def  __exit__(self, exc_type, exc_val, exc_tb):
        print ('__exit__')
p=A()
with  p  as  f:  # 此处的p是__enter__的返回值,是f的参数,若此处__enter__无return,则默认返回为None,无意义
    raise Exception('Error')  # 此处抛出异常,一般的,抛出异常后,语句将不会再次执行
    print (p==f) # 此处用于比较p和f的关系
    print (p is f)
    print (p)
    print (f)


由此证明,当异常抛出时,exit对应的语句仍然会被执行。

当在上下文环境中直接退出会怎么样

import  sys
class A:
    def  __init__(self):
        print ('init instance')
    def  __enter__(self):
        print ('__enter__')
        return 1
    def  __exit__(self, exc_type, exc_val, exc_tb):
        print ('__exit__')
p=A()
with  p  as  f:  # 此处的p是__enter__的返回值,是f的参数,若此处__enter__无return,则默认返回为None,无意义
    sys.exit()  # 此处的是直接退出
    print (p==f) # 此处用于比较p和f的关系
    print (p is f)
    print (p)
    print (f)


exit依然执行,此处满足上述清理工作,上下文管理非常安全,能够保证变量的顺利清除工作。

import  sys
class A:
    def  __init__(self):
        print ('init instance')
    def  __enter__(self):
        print ('__enter__')
        return self
    def  __exit__(self, exc_type, exc_val, exc_tb):
        print ('__exit__')
        print (exc_tb) #追踪信息
        print (exc_type)  # 类型
        print (exc_val)  # 值
        return  1  # 此处设置为1 是压制异常,不让其出现
p=A()
with  p  as  f:  # 此处的p是__enter__的返回值,是f的参数,若此处__enter__无return,则默认返回为None,无意义
    raise   Exception('Error1234454')
    print (p==f) # 此处用于比较p和f的关系
    print (p is f)
    print (p)
    print (f)

通过此方法进行函数执行时长计算
之前的计算时长方式

import  datetime
import  time
import  sys
def  wapper(fn):
    def _wapper(*args,**kwargs):
        start_time=datetime.datetime.now()
        ret  =  fn(*args,**kwargs)
        delta=(datetime.datetime.now()-start_time).total_seconds()
        print ("{} 函数的执行时间为: {}".format(fn.__name__,delta))
        return  ret
    return  _wapper
@wapper
def  add(x,y):
    time.sleep(2)
    return  x+y

add(4,5)


使用上下文管理的方式统计函数执行时间

import  datetime
import  time
class Timer:
    def  __init__(self,fn):
        self.fn=fn
    def  __enter__(self):
        self.start_time=datetime.datetime.now()
        return  self.fn  # 此处对应的是as前面的值
    def __exit__(self, exc_type, exc_val, exc_tb):
        delat=(datetime.datetime.now()-self.start_time).total_seconds()
        print ("函数{} 的执行时间为: {}".format(self.fn.__name__,delat))
        return  1
def  add(x,y):
    return  x+y
p=Timer(add)
with   p  as f:  # 此处调用的是__enter__的返回值,重命名为f
    time.sleep(2)
    print (f(4,5))

类装饰器的应用

import  datetime
import time
from functools import  wraps
class A:
    def __init__(self,fn):
        self.fn=fn
    def __call__(self,*args,**kwargs):  #实例调用支持的方法
        self.start_time=datetime.datetime.now()
        ret = self.fn(*args,**kwargs)
        delta=(datetime.datetime.now()-self.start_time).total_seconds()
        print ("{} 函数的执行时间为: {}".format(self.fn.__name__,delta))
        return  ret
@A   #add=A(add)
def add(x,y):
    time.sleep(2)
    return  x+y
print  (add(10,20))


进行属性覆盖如下

import  datetime
import time
from functools import  wraps
class A:
    def __init__(self,fn):
        self.fn=fn
    def __call__(self,*args,**kwargs):  #实例调用支持的方法
        self.start_time=datetime.datetime.now()
        ret = self.fn(*args,**kwargs)
        delta=(datetime.datetime.now()-self.start_time).total_seconds()
        print ("{} 函数的执行时间为: {}".format(self.fn.__name__,delta))
        return  ret
@A   #add=A(add)
def add(x,y):
    '''this is function'''
    time.sleep(2)
    return  x+y
print  (add(10,20))
print  (add.__doc__) # 此处打印出文档

import  datetime
import time
from functools import  wraps
class A:
    def __init__(self,fn):
        self.__doc__=fn.__doc__  # 此处只能进行部分的属性覆盖操作
        self.__name__=fn.__name__
        self.fn=fn
    def __call__(self,*args,**kwargs):  #实例调用支持的方法
        self.start_time=datetime.datetime.now()
        ret = self.fn(*args,**kwargs)
        delta=(datetime.datetime.now()-self.start_time).total_seconds()
        print ("{} 函数的执行时间为: {}".format(self.fn.__name__,delta))
        return  ret
@A   #add=A(add)
def add(x,y):
    '''this is function'''
    time.sleep(2)
    return  x+y
print  (add(10,20))
print  (add.__doc__) # 此处打印出文档

import  datetime
import time
from functools import  wraps
class A:
    def __init__(self,fn):
        wraps(fn)(self)  # 调用此方法完成属性的覆盖操作,此处第一个是原函数,后面是现在的函数
        self.fn=fn
    def __call__(self,*args,**kwargs):  #实例调用支持的方法
        self.start_time=datetime.datetime.now()
        ret = self.fn(*args,**kwargs)
        delta=(datetime.datetime.now()-self.start_time).total_seconds()
        print ("{} 函数的执行时间为: {}".format(self.fn.__name__,delta))
        return  ret
@A   #add=A(add)
def add(x,y):
    '''this is function'''
    time.sleep(2)
    return  x+y
print  (add(10,20))
print  (add.__doc__) # 此处打印出文档

上下文的应用场景

1 增强功能
在代码执行的前后增加代码,以增强其功能,类似装饰器的功能
2 资源管理
打开了资源需要关闭,例如文件对象,网络链接,数据库链接等
3 权限验证
在执行代码之前,做权限的验证,在enter 中处理
在代码进入的时候进行处理,在权限出去则不管


转载:https://blog.csdn.net/qq_29983883/article/details/105982314
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场