python全栈27讲_Python全栈day26-27(面向对象进阶)

参考 http://www.cnblogs.com/linhaifeng/articles/6204014.html

1,什么是反射

反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问、检测和修改它本身状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反射性的研究。它首先被程序语言的设计领域所采用,并在Lisp和面向对象方面取得了成绩。

四个可以自省的函数

hasattr(object,name)

getattr(object,name)

setattr(object,name)

delattr(object,name)

反射1.py

class BlackMedium:

feture = 'Ugly'

def __init__(self,name,addr):

self.name = name

self.addr = addr

def sell_hourse(self):

print('[%s] 正在卖房子' %self.name)

def rent_hourse(self):

print('[%s] 正在租房子' % self.name)

b1 = BlackMedium('世华地产','天露园')

#检测b1有没有name数据属性

#检测传递的name参数是一个字符串

print(hasattr(b1,'name'))

#True

#检测b1有没有对应的函数属性

print(hasattr(b1,'sell_hourse'))

#True

#使用getattr取到对应的数据属性和函数属性

#如果没有则报错

#如果不想报错可以接一个字符串,找不到则输出那个字符串

#例如getattr(b1,'name','没有这个参数')

print(getattr(b1,'name'))

#世华地产

#取得函数属性是一个对象

print(getattr(b1,'sell_hourse'))

#>

#把以上对象赋值给一个变量加()就运行了

#相当于b1.rent_hourse()

func=getattr(b1,'sell_hourse')

func()

#setattr设置属性加一个属性,或者修改一个属性

setattr(b1,'name','SB')

print(b1.__dict__)

#{'addr': '天露园', 'sb': True, 'name': '世华地产'}

#delattr删除一个属性

#把name删除了就剩下一个地址了

delattr(b1,'name')

print(b1.__dict__)

#{'addr': '天露园'}

反射的作用,假如程序员A,B同时写一个功能,B的程序需要调用A的类,假如A临时出差了,B可以直接继续写自己的代码,待A回来完善代码即可

反射的作用.py

#程序员A写的代码ftp代码

class FtpClient:

#ftp客户端但是还没有实现功能

def __init__(self,addr):

print('正在连接服务器[%s]'%addr)

self.addr=addr

#程序员B需要调用A的代码

#from module import FtpClient

f1=FtpClient('192.168.1.1')

#调用前判断里面是否有这个方法,如果有则使用getattr取出来用

#如果没有就先写其他逻辑

if hasattr(f1,'get'):

func_get=getattr(f1,'get')

func_get()

else:

print('--->不存在此方法')

print('处理其他逻辑')

动态模块导入

目录结构如下

t.py

def test1():

print('test1')

def _test2():

print('test2')

动态模块导入1.py

#from m1 import t

module_t=__import__('m1.t')

#不管嵌套多少层拿到的是顶层的m1

print(module_t)

#

#调用

module_t.t.test1()

动态模块导入2.py

#私有属性无法使用*来导入但是可以直接使用_test2来导入

from m1.t import test1,_test2

test1()

_test2()

动态模块导入3.py

#使用模块导入

import importlib

#获取的对象不是m1而是m1.t

m=importlib.import_module('m1.t')

#

print(m)

m.test1()

m._test2()

2,类的内置attr属性

双下划线开头的attr方法__getattr__.py

class Foo:

x=1

def __init__(self,y):

self.y=y

def __getattr__(self, item):

print('执行__getattr__')

f1 = Foo(10)

#调用一个不存在的数据属性才会触发__getattr__

f1.x1

#执行__getattr__

只有在属性不存在时,会自动触发(有用其余两个用的少)

双下划线开头的attr方法__delattr__.py

class Foo:

x=1

def __init__(self,y):

self.y=y

def __delattr__(self, item):

print('删除操作__delattr__')

f1=Foo(10)

#删除一个属性会触发__delattr__

del f1.y

#删除操作__delattr__

删除属性时会触发

双下划线开头的attr方法__setattr__.py

class Foo:

x=1

def __init__(self,y):

self.y=y

def __setattr__(self, key, value):

print('执行__setattr__')

#self.key=value

self.__dict__[key]=value

#如果是直接self.key=value赋值执行会导致递归溢出

#需要使用self.__dict__[key]=value直接对字典进行操作

f1=Foo(10)

#执行__setattr__

print(f1.__dict__)

#{'y': 10}

设置属性时会触发

双下划线开头的attr方法综合.py

class Foo:

x=1

def __init__(self,y):

self.y=y

def __getattr__(self, item):

print('---->from getattr:你找的属性不存在')

def __setattr__(self, key, value):

#正确的使用方法

self.__dict__[key]=value

def __delattr__(self, item):

#正确的使用方法

self.__dict__.pop(item)

__getattr__的用处:当对象调用一个属性时,假如对象没有这个属性则会报错,使用内部__getattr__方法,当属性不存在的时候执行,可以打印出对应的错误信息

双下划线开头的__getattr__方法使用.py

class Foo:

def __init__(self,name):

self.name=name

def __getattr__(self, item):

print('你找的属性[%s]不存在'%item)

f1=Foo('zhangsan')

#打印对象有的属性正常输出

print(f1.name)

#zhangsan

#执行一个没有的属性则触发内部的__getattr__

#输出你找的属性不存在

f1.age

#你找的属性[age]不存在

__setattr__的用处,定制赋值类型或者对赋值进行相应的处理

双下划线开头的__setattr__方法使用.py

class Foo:

def __init__(self,name):

self.name=name

def __getattr__(self, item):

print('你找的属性[%s]不存在'%item)

def __setattr__(self, k, v):

print('执行setattr',k,v)

if type(v) is str:

print('开始设置')

#self.k=v #假如这样赋值相当下面的f1.age=18 会再次触发setattr导致无限循环

self.__dict__[k]=v.upper()

else:

print('必须是字符串类型')

#实例化对象会调用内部的__setattr__方法执行打印操作

#这个是自定义的setattr会替代系统默认的setattr执行

#系统默认的setattr就会执行赋值操作

#但是不会执行赋值操作

f1=Foo('zhangsan')

#执行setattr

f1.age=18

#因为没有执行赋值操作所以对象f1的字典为空

#执行了赋值操作就有对应的值

print(f1.__dict__)

#{}

__delattr__的用处:可以用于设置用户不允许删除对应的属性

双下划线开头的__delattr__方法使用.py

class Foo:

def __init__(self,name):

self.name=name

def __getattr__(self, item):

print('你找的属性[%s]不存在'%item)

def __setattr__(self, k, v):

print('执行setattr',k,v)

if type(v) is str:

print('开始设置')

#self.k=v #假如这样赋值相当下面的f1.age=18 会再次触发setattr导致无限循环

self.__dict__[k]=v.upper()

else:

print('必须是字符串类型')

def __delattr__(self, item):

print('不允许删除属性[%s]'%item)

#print('执行delattr',item)

#del self.item #不能这样删除也会触发无限循环

#self.__dict__.pop(item)

f1=Foo('zhangsan')

del f1.name

print(f1.__dict__)

当用户调用删除方法时候回自动触发自定义的__delattr__方法,然后在该方法里面不进行任何操作,即可保证用户无法误删除

通过继承和派生实现包装

包装标准类型.py

class List(list):

def append(self, p_object):

if type(p_object) is str:

#self.append(p_object)

#如果直接使用append还会导致无限循环,所以直接直接使用super调用父类的方法

super().append(p_object)

else:

print('只能添加字符串类型')

def show_midlle(self):

mid_index=int(len(self)/2)

return self[mid_index]

l2 = list('helloworld')

print(l2,type(l2))

l1=List('helloworld')

l1.append('111')

l1.append(11)

print(l1,type(l1))

#print(l1.show_midlle())

使用定义一个新类List继承list

一,可以在里面定义list没有的方法,比如调用函数取出列表中间的元素

二,重新定义append方法让新类在append的时候只接收字符串类型,不接收其他数据类型

这种方式叫做通过继承的方式完成包装,通过包装的方式定制自己的数据类型

组合发方式完成授权

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

实现授权的关键点就是覆盖__getattr__方法

授权.py

import time

class FileHandle:

def __init__(self,filename,mode='r',encoding='utf-8'):

#file得到的是一个文件句柄

self.file=open(filename,mode,encoding=encoding)

self.mode=mode

self.encoding=encoding

def write(self,line):

t=time.strftime('%Y-%m-%d %X')

self.file.write('%s %s'%(t,line))

#文件操作里面有什么就继承了什么(read,write,seek)

def __getattr__(self, item):

print(item,type(item))

#返回对应的方法如果输入的是read则返回read方法

#如果输入的是write则返回的是write方法

return getattr(self.file,item)

f1=FileHandle('a.txt','w+')

print(f1.read)

#

print(f1.write)

#

f1.write('内存高\n')

f1.write('硬盘高\n')

f1.seek(0)

print(f1.read())

通过__getattr__的方法实现自定义数据格式,file得到的是一个open打开的文件句柄

当执行f1.read或者其他的时候因为类本身没有对应的方法,就执行__getattr__然后通过getattr返回对应的方法read或者write

调用方法直接加()

但是假如只有__getattr__方法相当于直接原样调用open打开句柄的方法,没有什么意义

另外自定义write方法实现所有写的数据前面加上时间信息

PS:只有在对对象操作的时候才会触发__setattr__ ,__getattr__,__delattr__ 对类操作的时候不会触发

isinstance和issubclass判断一个实例是不是由某一个类产生以及判断一个类是不是另外一个类的子类

isinstance和issubclass.py

class Foo:

pass

f1=Foo()

class Bar(Foo):

pass

#isinstance判断对象是否是类实例化出来的

print(isinstance(f1,Foo))

#True

#isinstance判断是否是子类

print(isinstance(Bar,Foo))

#True

内置方法__getattribute__

getattribute.py

class Foo:

def __init__(self,x):

self.x=x

def __getattr__(self, item):

print('执行的是getattr')

def __getattribute__(self, item):

print('执行的是getattribute')

raise AttributeError('抛出异常了')

f1=Foo(10)

f1.xxxxx

#执行的是getattribute

#执行的是getattr

如果内置重新定了了__getattr__和__getattribute__则执行了有的属性会执行__getattribute__执行了没有的属性还是会执行__getattribute__不执行__getattr__,只有在内部定义了raise AttributeError('抛出异常了')才会先执行__getattribute__然后执行__getattr__

__getattribute__在任何调用的时候都会执行,__getattr__在__getattribute__抛出异常的时间接着执行,如果没有定义__getattr__则程序直接报错,不写__getattribute__会按照系统抛出异常

item方法:与getattr setattr delattr方法类似,不同的是需要通过对字典的操作才触发

item方法.py

class Foo:

#自定义getitem返回的是key对应的键值

def __getitem__(self, item):

print('getitem')

return self.__dict__[item]

def __setitem__(self, key, value):

print('setitem')

self.__dict__[key]=value

def __delitem__(self, key):

print('delitem')

self.__dict__.pop(key)

f1=Foo()

#打印字典开始为空

print(f1.__dict__)

#{}

#操作字典才会触发对应的item方法

#如果使用f1.name='zhangsan'这样对实例的赋值不会触发

f1['name'] = 'zhangsan'

#setitem

del f1['name']

#delitem

#使用字典赋值会触发setitem

f1['age']=18

#setitem

#使用字典取值会先触发getitem

#然后在打印返回值18

print(f1['age'])

#getitem

#18

系统默认的对象字符串显示不明显,可以使用内置的__str__方法重新定义

改变对象的字符串显示.py

class Foo:

def __init__(self,name,age):

self.name=name

self.age=age

def __str__(self):

return '这是str'

def __repr__(self):

return '名字是%s 年龄是%s' %(self.name,self.age)

f1=Foo('zhangsan',18)

print(f1)

str函数或者print函数--->obj.__str__()

repr优先用于交互式解释权

如果__str__没有定义,你们会使用__repr__来代替输出

如果__str__定义了使用__str__输出,在交互式解释器使用__repr__输出,就算__str__定义了也不输出

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

自定制格式化方式format

调用格式化函数format相当于调用类的内部函数__format__可以通过自定义的方式定义自己的格式化方式

自定制格式化方式format.py

#默认的自定义格式化方式

#0代表的就是第一个位置参数

x='{0}{0}{0}'.format('dog')

print(x)

#dogdogdog

#定义日期类,包含年月日

format_dic={

'ymd':'{0.year}{0.mon}{0.day}',

'y-m-d':'{0.year}-{0.mon}-{0.day}',

'y:m:d':'{0.year}:{0.mon}:{0.day}'

}

class Date:

def __init__(self,year,mon,day):

self.year=year

self.mon=mon

self.day=day

def __format__(self, format_spec):

#默认format_spec为空,如果用户没有传递则给一个默认的日期格式ymd

#如果用户传递的不在定义的字典里面也给一个默认的日期格式ymd

if not format_spec or format_spec not in format_dic:

format_spec='ymd'

#如果为空则fm='{0.year}{0.mon}{0.day}'

fm=format_dic[format_spec]

#返回 '{0.year}{0.mon}{0.day}'.format(self)

return fm.format(self)

d1=Date(2018,3,1)

x='{0.year}{0.mon}{0.day}'.format(d1)

print(x)

#201831

y='{0.year}-{0.mon}-{0.day}'.format(d1)

print(y)

#2018-3-1

z='{0.year}:{0.mon}:{0.day}'.format(d1)

print(z)

#2018:3:1

print(format(d1,'ymd'))

#201831

print(format(d1,'y-m-d'))

#2018-3-1

print(format(d1,'y:m:d'))

#2018:3:1

#传递的不在字典里面使用默认输出格式

print(format(d1,'ysdf'))

#201831

slots

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

2.引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)

3.为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__

当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个

字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给

实例添加新的属性了,只能使用在__slots__中定义的那些属性名。

4.注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该

只在那些经常被使用到 的用作数据结构的类上定义__slots__比如在程序中需要创建某个类的几百万个实例对象 。

关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。 更多的是用来作为一个内存优化工具。

slots属性.py

class Foo:

__slots__ = ['name','age'] #{'name':None,'age':None}

#__slots__ = 'name'

f1=Foo()

f1.name='zhangsan'

print(f1.name)

f1.age=18

print(f1.age)

#没有字典属性了打印报错

#print(f1.__dict__)

print(f1.__slots__)

#['name','age']

#只能操作slots定义的属性不能添加新的属性,否则报错

#f1.gender='男'

doc描述信息

doc属性.py

class Foo:

'我是描述信息'

pass

print(Foo.__doc__)

#我是描述信息

#该属性无法被继承

class Bar(Foo):

pass

#无法继承返回为None

print(Bar.__doc__)

#None

__module__和__class__

取到实例来自哪个模块及来自哪个类

module和class.py

from lib.aa import C

c1 = C()

print(c1.name)

#来自哪个模块

print(c1.__module__)

#lib.aa

#由哪个类产生

print(c1.__class__)

#

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

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

析构方法.py

class Foo:

def __init__(self,name):

self.name=name

def __del__(self):

print('执行了')

f1=Foo('zhangsan')

del f1.name

print('------>')

#输出为

#------>

#执行了

#为什么先输出------>

#因为删除属性不会执行del

#为什么又输出了执行了

#因为整个py程序执行完了

#假如语句改为

#del f1

#print('------>')

#则输出顺序调过来

#因删除了实例就执行了del

__call__

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

call方法.py

class Foo:

def __call__(self, *args, **kwargs):

print('实例执行了obj()')

f1=Foo()

#Foo()执行了 __init__()

#实例也可以加括号运行,调用的是类下面的__call__方法

f1()

#实例执行了obj()

__next__和__iter__实现迭代器协议

什么是迭代器协议

1,迭代器协议是指,对象必须提供一个next方法,执行该方法要么返回迭代的下一项,要么引起一个StopIteration异常,以终止迭代(只能往后不能倒退)

2,可迭代对象:实现了迭代器协议的对象(如何实现:对象内部定义一个__iter__()方法)

列表,元祖等本身可以连接是不可迭代对象因为他们没有next方法,但是在调用for或者while循环的时候是可迭代对象因为for循环内部自动对列表等使用了iter变成可迭代对象

迭代器协议1.py

l = 'hello'

#使用__iter__方法把列表变成可迭代对象

iter_l = l.__iter__()

print(iter_l)

#

#既然是可迭代对象就可以使用__next__方法依次调出元素

#next(iter_l效果和iter_l.__next__()是一样的

#如果已经调用完毕再使用next方法则会抛出StopIteration错误

#使用for循环的方式遍历完毕则不会输出了不会报错

print(iter_l.__next__())

print(iter_l.__next__())

print(iter_l.__next__())

print(iter_l.__next__())

print(next(iter_l))

print(next(iter_l))

迭代器协议2.py

class Foo:

pass

f1 = Foo()

#把实例化出来的对象使用for循环遍历报错,Foo不是一个可迭代对象

#报错代码为

#TypeError: 'Foo' object is not iterable

for i in f1:

print(i)

执行for循环相当于执行了 f1.__iter__()在内部定义一个__iter__()函数

迭代器协议3.py

class Foo:

def __iter__(self):

pass

f1 = Foo()

for i in f1:

print(i)

执行报错iter() returned non-iterator of type 'NoneType' 因为__iter__没有返回值

定义__iter__函数返回自己

迭代器协议4.py

class Foo:

def __iter__(self):

return self

f1 = Foo()

for i in f1:

print(i)

执行还是报错iter() returned non-iterator of type 'Foo'

在函数内部定义一个__next__()并且定义一个初始化函数__init__

迭代器协议5.py

class Foo:

def __init__(self,n):

self.n=n

def __iter__(self):

return self

def __next__(self):

self.n+=1

return self.n

f1 = Foo(10)

for i in f1:

print(i)

执行会陷入一个无限循环

定义迭代器终止条件

class Foo:

def __init__(self,n):

self.n=n

#有__iter__方法就能变成迭代器

def __iter__(self):

return self

def __next__(self):

if self.n == 13:

raise StopIteration('终止了')

self.n+=1

return self.n

# l=list('hello')

# for i in l:

# print(i)

f1=Foo(10)

# print(f1.__next__())

# print(f1.__next__())

# print(f1.__next__())

# print(f1.__next__())

#

for i in f1:

print(i)

当self.n值等于13的时候则抛出StopIteration错误

迭代器实现斐波那契数列.py

class Fib:

def __init__(self):

self._a=0

self._b=1

def __iter__(self):

return self

#实现数组除了第一二位为1,1其余位数为前面两位数相加

def __next__(self):

if self._a > 100:

raise StopIteration('终止了')

self._a,self._b = self._b,self._a + self._b

return self._a

f1=Fib()

print(next(f1))

print(next(f1))

print(next(f1))

print(next(f1))

print(next(f1))

for i in f1:

print(i)

输出为定义了一个条件当数组数字大于100时候终止

1

1

2

3

5

8

13

21

34

55

89

144

描述符是什么

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

__get__():调用一个函数属性时触发

__set__():w为一个属性赋值时,触发

__delete()__:采用del删除属性时,触发

描述符1.py

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 = 'zhangsan'

del f1.name

什么时候执行,需要在另外一个类定义成这个类的类属性

描述符2.py

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 = 'zhangsan'

del f1.name

class Bar:

x = Foo()

b1 = Bar()

#打印b1字典为一个空字典

print(b1.__dict__)

#{}

#调用函数属性触发__get__

b1.x

#===>get方法

#设置函数属性触发__set__

b1.x=1

#===>set方法

#删除函数属性触发__delete__

del b1.x

#===>delete方法

描述符是干什么的

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

描述符分两种

一,数据描述符:至少实现了__get__()和__set__()

二,非数据描述符:没有实现__set__()

注意事项:

一 描述符本身应该定义成新式类,被代理的类也应该是新式类

二 必须把描述符定义成这个类的类属性,不能为定义到构造函数中(__init__)

三 要严格遵循该优先级,优先级由高到底分别是

1.类属性

2.数据描述符

3.实例属性

4.非数据描述符

5.找不到的属性触发__getattr__()

描述符优先级1_类属性大于数据描述符.py

class Foo:

def __get__(self, instance, owner):

print('===>get方法')

def __set__(self, instance, value):

print('===>set方法')

def __delete__(self, instance):

print('===>delete方法')

class Bar:

x=Foo()

def __init__(self,n):

self.x=n

# print(Bar.x)

#直接给类属性赋值,因为类属性优先级高于数据描述符所以没有触发__set__

#直接给类属性赋值了

Bar.x=1

print(Bar.x)

#1

描述符优先级1_数据描述符大于实例属性.py

class Foo:

def __get__(self, instance, owner):

print('===>get方法')

def __set__(self, instance, value):

print('===>set方法')

def __delete__(self, instance):

print('===>delete方法')

class Bar:

x=Foo()

def __init__(self,n):

self.x=n

#实例化触发了set方法

b1=Bar(10)

#===>set方法

#调用触发了get方法

b1.x

#===>get方法

#给实例属性赋值触发set方法

b1.x=1

#打印实例属性触发了get方法但是get方法只进行了打印操作没有返回值

print(b1.x)

#===>get方法

#None

#所以数据描述符的优先级大于实例属性

描述符优先级1_实例属性大于非数据描述符.py

class Foo:

def __get__(self, instance, owner):

print('===>get方法')

# def __set__(self, instance, value):

# print('===>set方法')

# def __delete__(self, instance):

# print('===>delete方法')

class Bar:

x=Foo()

# def __init__(self,n):

# self.x=n

b1 = Bar()

#给实例赋值

b1.x = 11

#实例属性大于非数据描述符

print(b1.x)

#11

#PS Foo不能有__set__和__delete__方法就是非数据描述符

描述符优先级1_非数据描述符大于找不到.py

class Foo:

def __get__(self, instance, owner):

print('===>get方法')

# def __set__(self, instance, value):

# print('===>set方法')

# def __delete__(self, instance):

# print('===>delete方法')

class Bar:

x=Foo()

# def __init__(self,n):

# self.x=n

def __getattr__(self, item):

print('===>getattr')

b1 = Bar()

#给实例赋值

b1.x = 11

#实例属性大于非数据描述符

print(b1.x)

#11

b1.xxxx

#===>getattr

调用b1.xxxx因为找不到这个属性触发了Bar里面定义的__getattr__

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值