【python】面向对象进阶

 isinstance(obj,cls) 和 issubclass(sub,super)

isinstance(obj, cls):检查一个对象是否是一个类的实例

class Foo(object):
    pass
  
obj = Foo()
  
isinstance(obj, Foo)

issubclass(sub, super)检查sub类是否是super类的派生类

class Foo(object):
    pass
  
class Bar(Foo):
    pass
 
issubclass(Bar, Foo)

 

二 反射/自省

主要指程序可以访问,检测和修改它本身状态或行为的一种能力(自省)

反射的四种方法:

  hasattr, getattr, setattr, delattr

1 判断object中有没有一个name字符串对应的方法或属性
hasattr(object,name)

相当于object.name

def getattr(object, name, default=None): # known special case of getattr
    """
    getattr(object, name[, default]) -> value

    Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
    When a default argument is given, it is returned when the attribute doesn't
    exist; without it, an exception is raised in that case.
    """
    pass
getattr(object, name, default = None)

调用object.name ,即通过某个字符串调用类的属性(函数属性)

def setattr(x, y, v): # real signature unknown; restored from __doc__
    """
    Sets the named attribute on the given object to the specified value.

    setattr(x, 'y', v) is equivalent to ``x.y = v''
    """
    pass
setattr(object, key, value)

object.key = value

def delattr(x, y): # real signature unknown; restored from __doc__
    """
    Deletes the named attribute from the given object.

    delattr(x, 'y') is equivalent to ``del x.y''
    """
    pass
delattr(object, 'name')

del object.name

note:这四种方法都是针对的是对象,对类而言并不适用

 1 class BlackMedium:
 2     feature='Ugly'
 3     def __init__(self,name,addr):
 4         self.name=name
 5         self.addr=addr
 6 
 7     def sell_house(self):
 8         print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name)
 9     def rent_house(self):
10         print('%s 黑中介租房子啦,傻逼才租呢' %self.name)
11 
12 b1=BlackMedium('万成置地','回龙观天露园')   #实例化
13 
14 #检测是否含有某属性
15 print(hasattr(b1,'name'))
16 print(hasattr(b1,'sell_house'))
17 
18 #获取属性
19 n=getattr(b1,'name')
20 print(n)
21 func=getattr(b1,'rent_house')
22 func()
23 
24 # getattr(b1,'aaaaaaaa') #报错
25 print(getattr(b1,'aaaaaaaa','不存在啊'))
26 
27 #设置属性
28 setattr(b1,'sb',True)
29 setattr(b1,'show_name',lambda self:self.name+'sb')
30 print(b1.__dict__)
31 print(b1.show_name(b1))
32 
33 #删除属性
34 delattr(b1,'addr')
35 delattr(b1,'show_name')
36 delattr(b1,'show_name111')#不存在,则报错
37 
38 print(b1.__dict__)
四种方法应用

note:类本质上也是一个对象

 

三  __setattr , __delattr__, __getattr__

这三个是定义在类里面的

 1 class Foo:
 2     x=1
 3     def __init__(self,y):
 4         self.y=y
 5 
 6     def __getattr__(self, item):
 7         print('----> from getattr:你找的属性不存在')
 8 
 9     def __setattr__(self, key, value):
10         print('----> from setattr')
11         # self.key=value #无限递归,初始化__init__的时候会设置这个属性,会操作key和value,然后又会返回执行__setattr__,因此陷入死循环
12         # self.__dict__[key]=value #应该使用它
13 
14     def __delattr__(self, item):
15         print('----> from delattr')
16         # del self.item #无限递归
17         self.__dict__.pop(item)
18 
19 #__setattr__添加/修改属性会触发它的执行
20 f1=Foo(10)
21 print(f1.__dict__) # 因为重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
22 f1.z=3
23 print(f1.__dict__)
24 
25 #__delattr__删除属性的时候会触发
26 f1.__dict__['a']=3#我们可以直接修改属性字典,来完成添加/修改属性的操作
27 del f1.a
28 print(f1.__dict__)
29 
30 #__getattr__只有在使用点调用属性且属性不存在的时候才会触发
31 f1.xxxxxx
类的内置attr属性

其中,对于self.key=value 本质上就是在执行__setattr__,将操作的属性放在属性字典中去,f1 =Foo(10)的执行一样,本质上就是在执行self.__dict__[key],都是在属性字典中添加。

为此,在override自定义之后,可以使得有选择的打印结果,example: self.__dict__[key] = value.upper()

 

四 二次加工标准类型

包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了我们刚学的继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

 1 class List(list):   #继承所有的list属性也可以派生出属于自己的新方法
 2   def show_middle(self):
 3     mid_index = int(len(self)/2)
 4     return self[mid_index]
 5 
 6   def append(self, p_object):   #派生自己的append,加上类型检查
 7     if type(p_object) is str:
 8       #self.append(p_object)    #永远调用自己了
 9       super().append(p_object)  #直接调用父类的方法,与list.append(slef, p_object)效果一样
10     else11       print("只能添加字符串类型")
12 
13 l1 = List('alex')
14 l1.append('3')
15 print(l1.show_middle)
包装示例1

授权:授权是包装的一个特性, 包装一个类型通常是对已存在的类型的一些定制,这种做法可以新建,修改或删除原有产品的功能。其它的则保持原样。授权的过程,即是所有更新的功能都是由新类的某部分来处理,但已存在的功能就授权给对象的默认属性。

note:授权也是一种包装,通过__getattr__方法

 1 import time
 2 class FileHandle:
 3     def __init__(self,filename,mode='r',encoding='utf-8'):
 4         self.file=open(filename,mode,encoding=encoding)
 5     def write(self,line):   #定制自己的写方法
 6         t=time.strftime('%Y-%m-%d %T')
 7         self.file.write('%s %s' %(t,line))
 8 
 9     def __getattr__(self, item):
10         return getattr(self.file,item)
11 
12 f1=FileHandle('b.txt','w+')
13 f1.write('你好啊')
14 f1.seek(0)
15 print(f1.read())
16 f1.close()
授权示例1

  def __getattr__(self, item):   #通过__getattr__ 获得read,write等的方法

    print(item, type(item))  #type item is <str>

    return getattr(self.file, item)    #与继承实现同样的效果

 五 __getattribute__

class Foo:
    def __init__(self,x):
        self.x=x

    def __getattr__(self, item):
        print('执行的是我')
        # return self.__dict__[item]

f1=Foo(10)
print(f1.x)
f1.xxxxxx #不存在的属性访问,触发__getattr__
回顾__getattr__

  

 __getattribute__: 不管对象的属性是否存在,实例化调用的时候都会触发,当属性不存在的时候,会报给__getattr__

 

六 描述符(__get__, __set__, __delete__)

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

__get__():调用一个属性时,触发
__set__():为一个属性赋值时,触发
__delete__():采用del删除属性时,触发

class Foo: #在python3中Foo是新式类,它实现了三种方法,这个类就被称作一个描述符
    def __get__(self, instance, owner):
        pass
    def __set__(self, instance, value):
        pass
    def __delete__(self, instance):
        pass
定义一个描述符

6.2 描述符的作用:描述符是用来代理另外一个类的属性的(必须把描述符定义成这个类的类属性,不能定义到构造函数中)

class Foo:
    def __get__(self, instance, owner):
        print('触发get')
    def __set__(self, instance, value):
        print('触发set')
    def __delete__(self, instance):
        print('触发delete')

#包含这三个方法的新式类称为描述符,由这个类产生的实例进行属性的调用/赋值/删除,并不会触发这三个方法
f1=Foo()
f1.name='egon'
f1.name
del f1.name
描述符类产生的实例进行属性操作并不会触发这三个方法的执行

待完善:???描述符的应用

#描述符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__)

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

 

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

 1 #描述符Str
 2 class Str:
 3     def __get__(self, instance, owner):
 4         print('Str调用')
 5     def __set__(self, instance, value):
 6         print('Str设置...')
 7     def __delete__(self, instance):
 8         print('Str删除...')
 9 
10 class People:
11     name=Str()
12     def __init__(self,name,age): #name被Str类代理,age被Int类代理,
13         self.name=name
14         self.age=age
15 
16 
17 #基于上面的演示,我们已经知道,在一个类中定义描述符它就是一个类属性,存在于类的属性字典中,而不是实例的属性字典
18 
19 #那既然描述符被定义成了一个类属性,直接通过类名也一定可以调用吧,没错
20 People.name #恩,调用类属性name,本质就是在调用描述符Str,触发了__get__()
21 
22 People.name='egon' #那赋值呢,我去,并没有触发__set__()
23 del People.name #赶紧试试del,我去,也没有触发__delete__()
24 #结论:描述符对类没有作用-------->傻逼到家的结论
25 
26 '''
27 原因:描述符在使用时被定义成另外一个类的类属性,因而类属性比二次加工的描述符伪装而来的类属性有更高的优先级
28 People.name #恩,调用类属性name,找不到就去找描述符伪装的类属性name,触发了__get__()
29 
30 People.name='egon' #那赋值呢,直接赋值了一个类属性,它拥有更高的优先级,相当于覆盖了描述符,肯定不会触发描述符的__set__()
31 del People.name #同上
32 '''
类属性>数据描述符
 1 #描述符Str
 2 class Str:
 3     def __get__(self, instance, owner):
 4         print('Str调用')
 5     def __set__(self, instance, value):
 6         print('Str设置...')
 7     def __delete__(self, instance):
 8         print('Str删除...')
 9 
10 class People:
11     name=Str()
12     def __init__(self,name,age): #name被Str类代理,age被Int类代理,
13         self.name=name
14         self.age=age
15 
16 
17 p1=People('egon',18)
18 
19 #如果描述符是一个数据描述符(即有__get__又有__set__),那么p1.name的调用与赋值都是触发描述符的操作,于p1本身无关了,相当于覆盖了实例的属性
20 p1.name='egonnnnnn'
21 p1.name
22 print(p1.__dict__)#实例的属性字典中没有name,因为name是一个数据描述符,优先级高于实例属性,查看/赋值/删除都是跟描述符有关,与实例无关了
23 del p1.name
数据描述符>实例属性
 1 class Foo:
 2     def func(self):
 3         print('我胡汉三又回来了')
 4 f1=Foo()
 5 f1.func() #调用类的方法,也可以说是调用非数据描述符
 6 #函数是一个非数据描述符对象(一切皆对象么)
 7 print(dir(Foo.func))
 8 print(hasattr(Foo.func,'__set__'))
 9 print(hasattr(Foo.func,'__get__'))
10 print(hasattr(Foo.func,'__delete__'))
11 #有人可能会问,描述符不都是类么,函数怎么算也应该是一个对象啊,怎么就是描述符了
12 #笨蛋哥,描述符是类没问题,描述符在应用的时候不都是实例化成一个类属性么
13 #函数就是一个由非描述符类实例化得到的对象
14 #没错,字符串也一样
15 
16 
17 f1.func='这是实例属性啊'
18 print(f1.func)
19 
20 del f1.func #删掉了非数据
21 f1.func()
实例属性>非数据描述符

 

1 class Foo:
2     def func(self):
3         print('我胡汉三又回来了')
4 
5     def __getattr__(self, item):
6         print('找不到了当然是来找我啦',item)
7 f1=Foo()
8 
9 f1.xxxxxxxxxxx
非数据描述符>找不到

 

python是弱类型语言(不用定义变量的类型)即参数的赋值没有类型限制

通过描述符实现参数类型的限制:

class Typed:

  def  __init__(self, key, expected_type):

    self.key = key

    self.expected_type = expected_type

  def  __get__(self, instance, owner):

    print('get方法')

    print('instance参数【%s】' %instance)   //就是实例本身p1

    print('owner参数【%s】' %owner)      //就是实例p1的拥有者,就是类

    return instance.__dict__[self.key]

 

  def  __set__(self, instance, value):    

    print('set方法')

    print('instance参数【%s】' %instance)   //就是实例本身p1

    print('value参数【%s】' %value)

    if not isinstance(value, self.expected_type):

 

      raise TypeError('%s传入类型不是%s ' %(self.key, self.expected_type))

    instance.__dict__[self.key] = value

 

  def  __delete__(self, instance):

    print('delete方法')

    instance.__dict__.pop(self.key)

 

class People:

  name = Typed(’name',str)    //需要定义为数据描述符,如果是非数据描述符会调用实例属性的

  age = Typed('age', int)

  def  __init__(self, name, age, salary):

    self.name = name

    self.age = age

    self.salary = salary

    

p1 = People('zsb', 18, 33.3)  //触发set方法

p1.name

p1.name = 'egon'

print(p1.name)

print(p1.__dict__)   //没有name,被Typed代理了

 

类的装饰器

def Typed(**kwargs)

  def deco(obj):

    print('======>', kwargs)

    for key,val in kwargs.items():

      //obj.__dict__[key] = val

      setattr(obj,key,val)

    //obj.x= 1   //添加到Foo的属性字典中,给类在增加数据属性

    return obj

  print('==>',kwargs)

  return deco

@Typed(x=1, y=2, z=3)   //deco----->@deco   --->   Foo = deco(Foo)

//@deco(x=1, y=2, z=3)    //Foo = deco(Foo)

class Foo:

  pass

 

6.6 描述符总结:

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

class Lazyproperty:    #描述符也可以是类

  def __init__(self, func)

    print('=====>', func)

    self.func =func

  def  __get__(self, instance, owner):   #instance是实例本身,owner是实例所在的类

    print('get')

    if instance is None:

      return self

    res = self.func(instance)     //instance就是r1

    setattr(instance, self.func.__name__, res)   //添加到自己的字典中

    return res

  //def __set__:     //数据描述符优先级高于实例属性

  //  pass  

 

class Room:

  def __init__(self, name, width, length):

    self.name = name

    self.width = width

    self.length =length

  #@property   #给类定义的属性,area = property(area),同时触发函数area的运行

  @Lazyproperty   #area = Lazyproperty(area)  做的就是实例化,触发Lazyproperty下的__init__方法 "@"是一个语法塘,这个操作也是为类增加描述符的操作

  def area(self):

    return self.width* self.length

r1 = Room('bathroom', 1, 1)    //实例化

print(r1.area)     #由于r1的area方法被Lazyproperty所代理,得到的变成Lazyproperty object, trans:是由于语法塘的作用,将Lazyproperty的实例赋值给area,意味着类Room的属性字典中area对应的value变成了Lazyproperty的对象

#print(r1.area.func(r1))    #r1.area是一个对象,手动去触发

print(r1.__dict__)  #没有area,只能先找类的

print(Room.__dict__)   #有 area,是Lazyproperty的对象,因此,r1.area就会触发__get__

r1.area是调用实例r1的属性,而该属性被代理了,所以会触发__get__的方法

 

note:如果@Lazyproperty(),一定是先运行Lazyproperty,得到一个结果res,再运行@res,i.e. obj = res(res)

   如果@Lazyproperty,一定是直接运行obj = Lazyproperty(obj)

实例和类都可以调用静态属性,实际上静态属性是给实例用的

但是当类调用描述符的所代理的属性时,也会触发get方法,但是instance会变成None,所以会报错

 

6.7 自定制property实现延迟计算功能

对于property,每次运行都是要重复执行,而自定制的Lazyproperty可以在下一次运行中直接从上一次去拿结果

 

property底层就是基于描述符去实现的

 

 

 

七 __setitem__, __getitem__, __delitem__

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]时,我执行')
        self.__dict__.pop(key)
    def __delattr__(self, item):
        print('del obj.key时,我执行')
        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__)
三种方法使用

item和attr效果是一样的,item的操作是通过字典的方式去触发的,attr是通过“. ”方式去触发的

 

八 __str__, __repr__, __format__

 

f1 =Foo()

print(f1) -->  __str__(f1) ---->f1.__str__()

 print触发str  str(f1)   -->f1.__str__()

 

repr(f1)   --->f1.__repr__()  用于解释器

str和repr共存,print先找str,没有定义str会再找repr,

这两个方法的返回值必须是字符串,否则会抛出异常

 

format(d1) --->  d1.__format__()

 

九 __slots__

'''
1.__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)
2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)
3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个
字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给
实例添加新的属性了,只能使用在__slots__中定义的那些属性名。
4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该
只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。           更多的是用来作为一个内存优化工具。

'''
class Foo:
    __slots__='x'


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

class Bar:
    __slots__=['x','y']
    
n=Bar()
n.x,n.y=1,2
n.z=3#报错
__slots__使用

__slots__的使用,使得对象在实例化后不再具有__dict__的方法,相应的减少了内存的损耗,但是也使得对象不能再添加新的属性,只能调用类的固有属性

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

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

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

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

 

十 __next__和__iter__实现迭代器协议

for循环一个对象,需要先给对象iter(f1)    ==> f1.__iter__() 

iter: 将一个对象变为可迭代对象

class Foo:

  def __init__(self, n):

    slef.n = n

  def __iter__(self):

    return self

  def __next__(self):

    if self.n == 13:

      raise StopIteration(’终止了‘)

    self.n += 1

    return self.n

f1 = Foo()

print(f1.__next__())

print(next(f1))

for i in f1:    #obj = iter(f1)

  print(i)     #obj.__next__()

 

 1 class Fib:
 2     def __init__(self):
 3         self._a=0
 4         self._b=1
 5 
 6     def __iter__(self):
 7         return self
 8 
 9     def __next__(self):
10         self._a, self._b = self._b, self._a + self._b
11         return self._a
12 
13 f1=Fib()
14 
15 print(f1.__next__())
16 print(next(f1))
17 print(next(f1))
18 
19 for i in f1:
20     if i > 100:
21         break
22     print('%s ' %i,end='')
斐波那契数列

 

十一 __doc__

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

print(Foo.__doc__)
类的描述信息
class Foo:
    '我是描述信息'
    pass

class Bar(Foo):
    pass
print(Bar.__doc__) #该属性无法继承给子类
该属性无法继承

很好理解,__doc__作为类的描述信息,本质上旨在描述他所在的类的相关信息,子类继承的过程中,应去描述子类的相关信息,为此__doc__也不应该拥有继承的属性

 

十二 __module__ 和__class__

__module__  表示当前操作的对象在哪个模块

__class__      表示当前操作的对象的类是什么

 

 

十三  __del__

析构方法,当对象在内存中被释放时,自动触发执行

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

 1 class Foo:
 2     def __del__(self):
 3         print('执行我啦')
 4 
 5 f1=Foo()
 6 del f1
 7 print('------->')
 8 
 9 #输出结果
10 执行我啦
11 ------->
12 
13 class Foo:
14     def __del__(self):
15         print('执行我啦')
16 
17 f1=Foo()
18 # del f1
19 print('------->')
20 
21 #输出结果
22 ------->
23 执行我啦
析构的应用示例

输出结果相反,因为创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中

当程序结束时,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上下文管理

 

十四 __enter__和__exit__

在操作文件对象时候可以写为:

with open('a.txt') as f:   

open(),list, str ,int 都叫做factory函数,本质上也相当于一个类,open('a.txt')就是一个对象,with open("a.txt")就是获取一个对象,然后赋值给f

上述也叫做上下文管理协议,即with语句,

class Foo:

  def __init__(self, name):

    self.name = name

  def __enter__(self):

    print('执行enter')

    return self

  def __exit__(self, exc_type, exc_val, exc_tb):       //异常必须要有一个异常类

    print('执行exit')

    print(exc_type) //异常类

    print(exc_val)  // 异常值

    print(exc_tb)   //异常的追踪信息

    return True  //如果加上这个返回值,不会报异常

with Foo('a.txt') as f:   // with的过程触发__enter__,只有其返回值才赋值给f

  print(f)

  print(sfgsdgegw)   //有异常就直接执行了exit

 

enter在实例化赋值给f时触发,而exit的触发在文件操作结束时执行

只要exit运行,意味着整个with语句执行结束

with obj as f:

  'code block'

1. with obj --> 触发obj.__enter__(),拿到返回值

2. as f ---> f = 返回值

3. with obj as f 等同于  f = obj.__enter__()

4. 执行代码块

4.1 没有异常的情况下,整个代码块执行完毕后去触发__exit__,它的三个参数都为None 

4.2 有异常的请款修改,从异常出现的位置直接触发__exit__,

  a:如果__exit__的返回值为True,代表吞掉了异常

  b:如果__exit__的返回值不为True,代表吐出了异常

  c:__exit__的运行完毕代表了整个with语句的执行完毕

用途:

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

2. 在需要管理一些资源,比如文件,网络连接和锁的编译环境中,可以在__exit__中定制自动释放资源的机制

 

十五 __call__

对象后面加括号,触发执行。

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

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

obj = Foo() # 执行 __init__
obj()       # 执行 __call__
__call__方法的执行

 

十六 metaclass 元类

16.1 元类

python中一切皆对象

class Foo:
    pass

f1 = Foo()  #f1是通过Foo类实例化的对象

print(type(f1))     //Foo
print(type(Foo))  //type

产生类的类就是元类,也就是type

元类是用来控制如何创建类的

元类的实例是类,正如类的实例是对象

type是python中的一个内建元类,用来直接控制类

 1 #-------------------method1---------------
 2 class Foo:
 3     x =1
 4 
 5 f1 = Foo()
 6 print(Foo)
 7 print(Foo.__dict)
 8 print(f1.__dict__)
 9 
10 
11 #-------------------method2---------------
12 FFo = type('FFo', (object, ), {'x':1})
13 
14 f2 = FFo()
15 print(FFo)
16 print(FFo.__dict__)
17 print(f2.__dict__)
定义类的两种方式

def __init__(self, name, age):

  self.name = name

  self.age = age

FFo = type('FFo', (object, ), {'x':1, ’__init__‘: __init__, 'test': test})

 

封装属性、生成对象的过程解释:

 1 class Mytype(type):
 2   def  __init__(self, a, b, c):
 3     print('元类的构造函数执行')
 4     print(a)      #对象名,本例中是Foo  
 5     print(b)    #继承的类:()
 6     print(c)    #属性字典:{}
 7 
 8   def __call__(self, *args, **kwargs):   #在本例中这个self指的是Foo,位置参数传给*args,关键字参数传给**kwargs
 9     print('=====>')   #由于自定义的元类,当__call__方法也是自定义时,返回值为None,因此,在检查f1.__dict__的时候会报错,也可以看出__call__方法应该做实例化的事情
10     obj = object.__new__(self)   #由于这里传入的对象是一个类,__new__方法是创建一个对象,因此obj就是Foo产生的对象,本例中也就是f1,此时只是产生了这个类的对象,将它赋值给f1,但是这个时候f1.__dict__还是为空
11     self.__init__(obj, *args, **kwargs)     #实例调用类的方法和类调用类的方法不同,实例调用不用传值,而类是需要的 Foo.__init__(f1,*args,**kwargs)
12     return obj
13 
14 class Foo(metaclass = Mytype):    #此时Foo的元类是Mytype,因此实际上做的是Mytype(self,'Foo', (), { }),也就是调用Mytype()进行实例化,类似于Foo = Mytype(),因此触发Mytype的 __init__方法,得到Foo这个类,也是Mytype的对象
15    def  __init__(self, name):       #在本例中,这个self指的是Foo的对象f1
16     self.name =name         #经过__call__方法,这里执行的就是f1.name = name,把name的属性封装到属性字典中 
17 
18 f1 = Foo('alex')      #由于Foo继承了自定义的元类,因此不会自动执行__init__方法,这里想要做的是将类Foo实例化为f1,实际上只是Foo()的执行,然后就会去调用Mytype中的__call__方法

note:默认类中,之所以实例化的时候(运行这个类)会自动触发__init__,是因为在__call__方法中一定要执行__init__

 

posted on 2020-10-26 17:23  薛定谔's猫  阅读(84)  评论(0)    收藏  举报

导航