python 类(object)的内置函数



# python 类(object)的内置函数

### 首先

#### 以__双下划线开头的内置函数 __

#### __往往会在某些时候被自动调用,例如之前了解的__next__的函数,和__init__函数,

这类函数 还有一些常用的:

#### 1 .isintance  2. issubclass

```python
isinstance(obj , cls)
class Foo (object):
    pass
obj = Foo ()
isinstance(obj ,Foo)   >>>True
检查是否obj 是否是类的的对象

isubclass
class Foo (object):
    pass
class Bar(Foo)

issubclass(bar,Foo)  >>>True

```

#### 3.1操作独享属性时自动触发

```
__setattr__
```

 使用点语法添加/修改属性会触发它的执行

```
__delattr__
```

 使用点语法删除属性的时候会触发

```
__getattr__
```

 使用点语法调用属性且属性不存在的时候才会触发

```
__getattribute__
```

 使用点语法调用属性的时候触发,无论属性是否存在都会执行

** 注意:当__getattribute__与__getattr__同时存在时,仅执行__getattribute__**

```python
class Foo:
    x=1
    def __init__(self,y):
        self.y=y
    def __getattr__(self, item):
        print('----> from getattr:你找的属性不存在')
    def __setattr__(self, key, value):
        print('----> from setattr')
        # self.key=value #这就无限递归了
        # self.__dict__[key]=value #应该使用它
    def __delattr__(self, item):
        print('----> from delattr')
        # del self.item #无限递归了
        self.__dict__.pop(item)

#__setattr__添加/修改属性会触发它的执行
f1=Foo(10)
print(f1.__dict__) # 因为重写了__setattr__,凡是赋值操作都会触发它的运行,什么都没写,就是根本没有执行赋值操作,除非手动操作属性字典,否则无法赋值
f1.z=3
print(f1.__dict__)

#__delattr__删除属性的时候会触发
f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)

#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx

```

### 3.2操作对象属性时自动触发

```
__setitem__
```

 使用key的形式添加/修改属性时触发

```
__getitem__
```

 使用key的形式获取属性时触发

```
__delitem__
```

 使用key的形式删除属性时触发

```
class Foo:
    def __init__(self,name):
        self.name=name

    def __getitem__(self, item):
        print(self.__dict__[item])

    def __setitem__(self, key, value):
        self.__dict__[key]=value
    def __delitem__(self, key):
        print('del obj[key]时,__delitem__执行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key时,__delattr__执行')
        self.__dict__.pop(item)

f1=Foo('sb')
f1['age']=18
f1['age1']=19
del f1.age1
del f1['age']
f1['name']='alex'
print(f1.__dict__)

```



### 4.描述符

#### 4.1什么是描述符

描述符本质就是一个类,在这个新式类中,至少实现了`__get__()`,`__set__()`,`__delete__()`中的一个,

也被称为描述符协议
`__get__()`:调用一个属性时,触发
`__set__()`:为一个属性赋值时,触发
`__delete__()`:采用del删除属性时,触发

#### 4.2为什么需要描述符:

描述符的作用是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中),python底层的很多特性都是使用描述符来完成的,例如实例方法,`classmethod,staticmethod`等等!

简单的说:描述符可以检测到一个属性的访问和修改,从而对这些操作增加额外的功能逻辑;

```
描述符Str
class Str:
    def __get__(self, instance, owner):
        print('Str调用')
    def __set__(self, instance, value):
        print('Str设置...')
    def __delete__(self, instance):
        print('Str删除...')

#描述符Int
class Int:
    def __get__(self, instance, owner):
        print('Int调用')
    def __set__(self, instance, value):
        print('Int设置...')
    def __delete__(self, instance):
        print('Int删除...')

class People:
    name=Str()
    age=Int()
    def __init__(self,name,age): #name被Str类代理,age被Int类代理,
        self.name=name
        self.age=age

#何地?:定义成另外一个类的类属性

#何时?:且看下列演示

p1=People('alex',18)

#描述符Str的使用
p1.name
p1.name='egon'
del p1.name

#描述符Int的使用
p1.age
p1.age=18
del p1.age


print(p1.__dict__)
print(People.__dict__)
'''
{}

{'__module__': '__main__', 'name': <__main__.Str object at 0x000001CA97D1BB00>, 'age': <__main__.Int object at 0x000001CA97D1BB38>, '__init__': <function People.__init__ at 0x000001CA97D1CBF8>, '__dict__': <attribute '__dict__' of 'People' objects>, '__weakref__': <attribute '__weakref__' of 'People' objects>, '__doc__': None}

'''

#补充
print(type(p1) == People) #type(obj)其实是查看obj是由哪个类实例化来的
print(type(p1).__dict__ == People.__dict__)

'''
Str设置...
Int设置...
'''


# 描述符应用 以及执行时机
```

#### 4.3描述的分类



```
1.数据描述符
 至少实现了__get__()和__set__()两个方法

class Foo:
    def __set__(self, instance, value):
        print('set')
    def __get__(self, instance, owner):
        print('get')
2.非数据描述符
 没有实现__set__()方法

class Foo:
  def __get__(self, instance, owner):
    print('get')
```

#### 4.4注意事项

一 描述符本身应该定义成新式类,被代理的类也应该是新式类
二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中
三 要严格遵循该优先级,优先级由高到底分别是
 1.类属性
 2.数据描述符
 3.实例属性
 4.非数据描述符
 5.找不到的属性触发`__getattr__()`

#### 4.5描述符总结

描述符是可以实现大部分python类特性中的底层魔法,包括@classmethod,@staticmethd,@property甚至是__slots__属性

描述符是很多高级库和框架的重要工具之一,描述符通常是使用到装饰器或者元类的大型框架中的一个组件.

案列1:利用描述符原理完成一个自定制@property,实现延迟计算(本质就是把一个函数属性利用装饰器原理做成一个描述符:类的属性字典中函数名为key,value为描述符类产生的对象)

```
class Lazyproperty:
    def __init__(self,func):
        self.func=func
    def __get__(self, instance, owner):
        print('这是我们自己定制的静态属性,r1.area实际是要执行r1.area()')
        if instance is None:
            return self
        else:
            print('--->')
            value=self.func(instance)
            setattr(instance,self.func.__name__,value) #计算一次就缓存到实例的属性字典中
            return value

class Room:
    def __init__(self,name,width,length):
        self.name=name
        self.width=width
        self.length=length

    @Lazyproperty #area=Lazyproperty(area) 相当于'定义了一个类属性,即描述符'
    def area(self):
        return self.width * self.length

r1=Room('alex',1,1)
print(r1.area) #先从自己的属性字典找,没有再去类的中找,然后出发了area的__get__方法
print(r1.area) #先从自己的属性字典找,找到了,是上次计算的结果,这样就不用每执行一次都去计算

实现延迟计算功能
```

案例2: 利用描述符原理完成一个自定制@classmethod

```
class ClassMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback():
            print('在这里可以加功能啊...')
            return self.func(owner)
        return feedback

class People:
    name='linhaifeng'
    @ClassMethod # say_hi=ClassMethod(say_hi)
    def say_hi(cls):
        print('你好啊,帅哥 %s' %cls.name)

People.say_hi()

p1=People()
p1.say_hi()
#疑问,类方法如果有参数呢,好说,好说

class ClassMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback(*args,**kwargs):
            print('在这里可以加功能啊...')
            return self.func(owner,*args,**kwargs)
        return feedback

class People:
    name='linhaifeng'
    @ClassMethod # say_hi=ClassMethod(say_hi)
    def say_hi(cls,msg):
        print('你好啊,帅哥 %s %s' %(cls.name,msg))

People.say_hi('你是那偷心的贼')

p1=People()
p1.say_hi('你是那偷心的贼')

自己做一个@classmethod
```

案例3:利用描述符原理完成一个自定制的@staticmethod

```
class StaticMethod:
    def __init__(self,func):
        self.func=func

    def __get__(self, instance, owner): #类来调用,instance为None,owner为类本身,实例来调用,instance为实例,owner为类本身,
        def feedback(*args,**kwargs):
            print('在这里可以加功能啊...')
            return self.func(*args,**kwargs)
        return feedback

class People:
    @StaticMethod# say_hi=StaticMethod(say_hi)
    def say_hi(x,y,z):
        print('------>',x,y,z)

People.say_hi(1,2,3)

p1=People()
p1.say_hi(4,5,6)

自己做一个@staticmethod
```

### 5.再看property

一个静态属性property本质就是实现了get,set,delete三种方法

```
#用法1
class Foo:
    @property
    def AAA(self):
        print('get的时候运行')

    @AAA.setter
    def AAA(self,value):
        print('set的时候运行)

    @AAA.deleter
    def AAA(self):
        print('delete的时候运行')

#只有在属性AAA定义property后才能定义AAA.setter,AAA.deleter
f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

#==============================================用法二
class Foo:
    def get_AAA(self):
        print('get的时候运行')

    def set_AAA(self,value):
        print('set的时候运行')

    def delete_AAA(self):
        print('delete的时候运行')
    AAA=property(get_AAA,set_AAA,delete_AAA) #内置property三个参数与get,set,delete一一对应

f1=Foo()
f1.AAA
f1.AAA='aaa'
del f1.AAA

用法二
```

### 6.对象的显示相关函数

```
__str__
```

调用str函数或者print函数时自动执行,返回值作为显示内容

```
__repr__
```

调用repr或者交互式解释器输出对象是自动执行,返回值作为显示内容

注意:如果`__str__`没有被定义,那么就会使用`__repr__`来代替输出 
这俩方法的返回值必须是字符串,否则抛出异常

```
__format__
```

调用format函数时自动执行,用于定制对象的格式化输出,

format使用案例:

```
#{0.year}:{0.month}:{0.day} 这是一个格式化字符串 ,想到于"%s:%s:%s" year表示取对象的year属性值
date_dic={
    'ymd':'{0.year}:{0.month}:{0.day}',
    'dmy':'{0.day}/{0.month}/{0.year}',
    'mdy':'{0.month}-{0.day}-{0.year}',
}
class Date:
    def __init__(self,year,month,day):
        self.year=year
        self.month=month
        self.day=day

    def __format__(self, format_spec):
        if not format_spec or format_spec not in date_dic:
            format_spec='ymd'
        fmt=date_dic[format_spec]
        return fmt.format(self)

d1=Date(2016,12,29)
print(format(d1))
print('{:mdy}'.format(d1))
```

### 7.内存优化之`__slots__`

**1.`__slots_`_是什么:**

```
是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
```

**2.引子:**

```
使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的 需要给每一个实例创建一个字典)
```

**3.为何使用__slots__**:

```
字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示,实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个字典,这跟元组或列表很类似。
在__slots__中列出的属性名在内部被映射到这个数组的指定下标上。
使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
```

**4.注意事项:**

```
__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 __slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。 关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。
```

案例:

```
class Foo:
    __slots__=['name','age']

f1=Foo()
f1.name='alex'
f1.age=18
print(f1.__slots__)

#f1.y=2  报错
print(f1.__slots__) #f1不再有__dict__


f2=Foo()
f2.name='egon'
f2.age=19
print(f2.__slots__)

print(Foo.__dict__)
#f1与f2都没有属性字典__dict__了,统一归__slots__管,节省内存
```

### 8.迭代器协议之 `__next__`和`__iter__`

关于迭代器之前已经详细的探讨过

这里简单回顾一下:

```
class Foo:
    def __init__(self,start,stop):
        self.num=start
        self.stop=stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.num >= self.stop:
            raise StopIteration
        n=self.num
        self.num+=1
        return n

f=Foo(1,5)
from collections import Iterable,Iterator
print(isinstance(f,Iterator))

for i in Foo(1,5):
    print(i)
    ```1,2,3,4,
```

### 9.帮助文档`__doc__`

这是一个隐藏属性,用于获取类的帮助文档,其实就是类下面的多行注释

```
class Foo:
    '我是描述信息'
    pass

class Bar(Foo):
    pass
print(Foo.__doc__) 
#输出 我是描述
print(Bar.__doc__) #该属性无法继承给子类
#输出 None
```

需要注意的是,该属性不会被继承

### 10.`__module__和__class__`

`__module__` 表示当前操作的对象在那个模块
`__class__` 表示当前操作的对象的类是什么

```
class C:

    def __init__(self):
        self.name = 'SB'
#该类位于lib/aa.py文件中
```

在另一个文件中:

```
from lib.aa import C

obj = C()
print obj.__module__  # 输出 lib.aa,即:输出模块
print obj.__class__      # 输出 lib.aa.C,即:输出类
```

### 11. `__del__`析构方法

#### 什么是析构方法

析构看做构建的反义词,构建指一个东西从无到有,析构指一个东西从有到无

析构方法的特点是: 当对象在内存中被释放时,会自动触发执行。

#### 为什么需要析构方法

如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义`__del__`,因为python会自动完成所有资源的回收;
如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了`__del__`

#### 使用案例:

```
class Foo:

    def __del__(self):
        print('执行我del啦')

f1=Foo()
del f1
print('------->')

#输出 执行我del啦
#输出 ------->
class Foo:
    def __del__(self):
        print('执行我del啦')

f1=Foo()
# del f1
print('------->')
#输出 ------->
#输出 执行我del啦
```

你会发现就算不去调用`del`方法一样会出发执行`__del__`,这是因为你python解释器在程序运行结束时必须将所有资源全部释放,当然包括创建的f1对象;

#### 典型的应用场景:

创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中
当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源
这与文件处理是一个道理:

```
f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件
del f #只回收用户空间的f,操作系统的文件还处于打开状态

#所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是
f=open('a.txt')
#读写...
f.close()
#很多情况下大家都容易忽略f.close,这就用到了with上下文管理
```

### 12.上下文管理之`__enter__`和`__exit__`

#### 什么是上下文管理?

上下文指的是一种语境,属于语言科学,说起来很抽象,其实你已经在很多地方使用到他了,来看一个实例:

```
 with open('a.txt') as f:
  print(f.read())
```

在这个代码中python解释器分析出你的代码想要做的事情,然后在结束的时候自动帮你将资源释放了,with中的所有代码都在一个上下文中,你可以把他理解为一个代码范围

#### 为什么需要上下文管理

上面的例子可以看出不使用上下文管理完没有问题,需要程序员,在合适的位置编写代码来关闭文件资源,这其实是一种体力活完全没有技术含量;

所以使用上下文可以省略掉一些重复代码的编写工作,同时避免了一些粗心的程序员忘记做一些清理工作;

#### 如何使用

该协议包含两个方法

`__enter__` 出现`with`语句,对象的`__enter__`被触发,有返回值则赋值给as声明的变量

`__exit__` with中代码块执行完毕时执行

只要这个一个类实现了这两个方法就可以被with 语句使用

案例: 模拟open

```
class Open:
    def __init__(self,filepath,mode='r',encoding='utf-8'):
        self.filepath=filepath
        self.mode=mode
        self.encoding=encoding

    def __enter__(self):
        # print('enter')
        self.f=open(self.filepath,mode=self.mode,encoding=self.encoding)
        return self.f

    def __exit__(self, exc_type, exc_val, exc_tb):
        # print('exit')
        self.f.close()
        return True 
    def __getattr__(self, item):
        return getattr(self.f,item)

with Open('a.txt','w') as f:
    print(f)
    f.write('aaaaaa')
    f.wasdf #抛出异常,交给__exit__处理
```

**需要注意的是:**

1. `` __exit__()`中的三个参数分别代表异常类型,异常值和追溯信息

2. with语句中代码块出现异常时,会立即触发方法`__exit__`的执行,并将异常信息错误参数传入

3. with语句中代码块未出现异常正常结束时也会触发方法`__exit__`的执行,此时参数中的异常信息为空

4. 如果`__exit__()`返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

**总结:**

1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处

### 13`__call__`

`__call__`是一个函数,在对象被调用时执行,调用就是加括号()

注:构造方法的执行是由创建对象触发的,即:对象 = 类名() ;而对于 **call** 方法的执行是由对象后加括号触发的,即:对象() 或者 类()的区别

```
class Foo:
    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):

        print('__call__')
obj = Foo() # 执行 __init__
obj()       # 执行 __call__
```