反射:

使用字符串数据类型的变量名来使用变量

wwwh 即 what,where,why,how 这 4 点是一种学习方法

反射 :使用字符串数据类型的变量名来使用变量

1.文件中存储的都是字符串

2.网络上能传递的也最接近字符串

3.用户输入的也是字符串

上面的 3 种情况都是字符串,如果有这种情况的,需要操作类或者模块时,就需要用到反射

有 4 种应用

类调用静态属性

对象调用属性 和 方法

模块调用模块中的名字

调用自己模块中的名字

  1. a = 1
  2. import sys
  3. b = getattr(sys.modules['__main__'],'a')
  4. print(b) # 输出变量a的值,即1
  5. def func():print('11')
  6. c = getattr(sys.modules['__main__'],'func')
  7. print(c) # 输出func的内存地址
  8. getattr(sys.modules['__main__'],'func')() # 执行方法,输出11

执行输出:

1

11

Teacher 类 wahaha 方法的连续反射

  1. import sys
  2. class Teacher():
  3. def wahaha(self):print('wa')
  4. alex = getattr(sys.modules['__main__'],'Teacher')() # 获取当前模块的Teacher,并执行
  5. print(alex) # Teacher类的内存地址
  6. getattr(alex,'wahaha')() # 执行方法wahaha

执行输出:

<__main__.Teacher object at 0x00000203554EBF28>

wa

反射静态方法

  1. import sys
  2. class Teacher(object):
  3. @staticmethod
  4. def wahaha():print('wa')
  5. Teacher = getattr(sys.modules['__main__'],'Teacher') # # 获取当前模块的Teacher类
  6. getattr(Teacher,'wahaha')() # 执行类方法,注意加括号

执行输出:

wa

找对象

  1. import sys
  2. class Teacher(object):
  3. def wahaha(self):print('wa')
  4. @staticmethod
  5. def qqxing():print('qq')
  6. alex = Teacher()
  7. b = getattr(sys.modules['__main__'],'alex')
  8. print(b)

输出:

<__main__.Teacher object at 0x0000015469531048>

内置方法

len len(obj)

obj 对应的类中含有len方法,len(obj)才能正常执行

hash hash(obj) 是 object 类自带的

只有实现了hash方法,hash(ojb)才能正常执行

hash 是加加速寻址

  1. print(hash('str'))

执行输出:8500378945365862541

hash 之后的数字,就是内存地址

hash 之后,将 value 存储在对应的内存地址中

字典占用的内存相对的比,较用空间换时间

list 占用的内存比较少,但是没有字典快

过一分钟,再次查看,发现数据都变了

-8079646337729346465

二、str,repr

repr() 函数将对象转化为供解释器读取的形式

  1. print(repr('1'))
  2. print(repr(1))

执行输出:

‘1’

1

看起来,没啥作用

  1. print(str('1'))
  2. print(str(1))

执行输出:

1

1

  1. li = [1,2,3,4]
  2. print(li)

执行输出:

[1, 2, 3, 4]

  1. class A:
  2. def __init__(self,*args):
  3. self.args = list(args)
  4. li = A(1,2,3,4,5)
  5. print(li)

执行输出:

<__main__.A object at 0x000001AAB3CDB9E8>

在类中,所有对象,默认打印的是内存地址

改变对象的字符串显示str,repr

  1. class A:
  2. def __init__(self,*args):
  3. self.args = list(args)
  4. def __str__(self):
  5. return '[%s]' % (','.join([str(i) for i in self.args]))
  6. li = A(1,2,3,4,5)
  7. print(li) # 输出的结果是obj.__str__()的结果<br>print(str(li)) # 结果同上<br>print('%s'%li) # 结果同上

执行输出:

[1,2,3,4,5]

[1,2,3,4,5]

每一个对象,都有str方法

print 执行时,实际是调用了str方法

  1. class Teacher:
  2. def __init__(self,name,age):
  3. self.name = name
  4. self.age = age
  5. def __str__(self):
  6. return "Teacher's object %s" % self.name
  7. a = Teacher('alex',80)
  8. b = Teacher('egon',80)
  9. print(a) # 实际调用了__str__
  10. print(b)<br>print(repr(a)) # 打印对象的内存地址

执行输出:

Teacher’s object alex

Teacher’s object egon

<__main__.Teacher object at 0x0000012346B9BF28>
  1. class Teacher:
  2. def __init__(self,name,age):
  3. self.name = name
  4. self.age = age
  5. def __str__(self): # 重新定义内置方法
  6. return "Teacher's object %s" % self.name
  7. def __repr__(self): # 重新定义内置方法,为了做个性化输出
  8. return 'repr function %s'%self.name
  9. a = Teacher('alex',80)
  10. b = Teacher('egon',80)
  11. print(repr(a)) # 打印repr函数的返回值
  12. print(a.__repr__()) #调用__repr__方法
  13. print('%r'%a)
  14. print(str(a)) # 打印str函数的返回值

执行输出:

repr function alex

repr function alex

repr function alex

Teacher’s object alex

repr(obj)的结果和 obj.repr()是一样的

‘%r’%(obj)的结果和 obj.repr()是一样的

所有的输出,本质就是向文件中写

print 执行时,是去内部中寻找str方法

所以 print 没有输出不了的数据,因为每一个对象都有str方法

print 一个对象是,打印的是内存地址

  1. def repr(obj): # 归一化设计
  2. return obj.__repr__()
  3. print(repr('1'))

执行输出:’1’

repr 执行时,其实是调用repr方法

repr(obj)的结果和 obj.repr()是一样的

那么问题来了

  1. repr(1)
  2. repr('1')

的结果为什么不一样?

看下面的例子:

  1. class int:
  2. def __repr__(self):
  3. return str(1)
  4. a = int()
  5. print(repr(a))
  6. class str2:
  7. def __repr__(self):
  8. return "'%s'" % str(1)
  9. b = str2()
  10. print(repr(b))

执行输出:

1

‘1’

repr 的功能,之所以能还原数据原来的样子

是因为每一种数据对象的reprt都不一样

那么 repr 是做了归一化设计,接收一个对象。python 一切皆对象

每个对象都有repr方法。执行 rept,就执行了repr方法。

为什么做归一化设计呢?因为要更接近于面向函数编程

如果每一个对象,都要对象名.str这样执行,太麻烦了

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597656849610-62965889-4112-4e06-9bd4-92454fecb825.png)

使用函数的方法,要比类名.方法名 使用简单

当需要使用str的场景时找不到 str就找repr

当需要使用repr的场景时找不到repr的时候就找父类的 repr

双下 repr 是双下 str 的备胎

总结:

len() obj.len() 返回值是一致的

len() 的结果是依赖 obj.len()

hash() 的结果是依赖 obj.hash()

str() 的结果是依赖 obj.str()

print(obj) 的结果是依赖 obj.str()

%s 的结果是依赖 obj.str() 语法糖

#

repr() 的结果是依赖 obj.repr()

%r 的结果是依赖 obj.repr()

repr 是 str 的备胎

如果strrepr同时存在, 一定是选择 repr

推荐几本书

《核心编程 第 2 版》 有很多基础知识

《核心编程 第 3 版》

《流畅的 python》 有点难度,这本书比较火

《数据结构与算法》 机械工业出版社

对底层结构比较感兴趣的话,可以看《数据结构与算法》这本书

机械工业出版社,是比较权威的,

出的书是比较有代表性的

这本书,用例主要是用 Python 实现的

为什么 int 类型,可以使用%s,是因为 int 有_repr方法

object.repr()

‘%r’ # repr()

int.repr()

str.repr()

%r 也是语法糖,不只是只有@

凡是不是调用函数,而是使用用一些符号之类的,都是语法糖

== 也是语法糖,它在内部执行时,是调用了内置方法,python 解释器帮我们做了。

表面能使用的符号,内部其实是调用了方法,执行了计算过程。

三、format

format 执行,就是调用了format方法

  1. class A:
  2. def __init__(self,name,school,addr):
  3. self.name = name
  4. self.school = school
  5. self.addr = addr
  6. a = A('大表哥','oldboy','沙河')
  7. print(format(a))

执行输出:

<__main__.A object at 0x000001AD3B07BF28>

对象之所以能用 format,是因为 object 有这个format方法

查看 object 源码

  1. def __format__(self, *args, **kwargs): # real signature unknown
  2. """ default object formatter """
  3. pass

format 执行时,必须要有参数 format_spec 才行

查看源码

  1. def format(*args, **kwargs): # real signature unknown
  2. """
  3. Return value.__format__(format_spec)
  4. format_spec defaults to the empty string
  5. """
  6. pass

自定义一个format方法

  1. class A:
  2. def __init__(self,name,school,addr):
  3. self.name = name
  4. self.school = school
  5. self.addr = addr
  6. def __format__(self, format_spec):
  7. #format_spec = '{obj.name}-{obj.addr}-{obj.school}'
  8. return format_spec.format(obj=self) #此行的format_spec等同于上面一行
  9. a = A('大表哥','oldboy','沙河')
  10. format_spec = '{obj.name}-{obj.addr}-{obj.school}'
  11. print(format(a,format_spec))

执行输出:

大表哥-沙河-oldboy

这样就可以在外部规定 format 的输出格式

{obj.name}-{obj.addr}-{obj.school}是用花括号{}的

比如正常情况下,也是用{}表示一个占位符,它是不能边的

如果输出的字符串,需要加中括号[ ],可以这么写

  1. class A:
  2. def __init__(self,name,school,addr):
  3. self.name = name
  4. self.school = school
  5. self.addr = addr
  6. def __format__(self, format_spec):
  7. return format_spec.format(obj=self)
  8. a = A('大表哥','oldboy','沙河')
  9. format_spec = '[{obj.name}]-[{obj.addr}]-[{obj.school}]'
  10. print(format(a,format_spec))

执行输出:

[大表哥]-[沙河]-[oldboy]

下面一个列子

  1. format_dict={
  2. 'nat':'{obj.name}-{obj.addr}-{obj.type}',#学校名-学校地址-学校类型
  3. 'tna':'{obj.type}:{obj.name}:{obj.addr}',#学校类型:学校名:学校地址
  4. 'tan':'{obj.type}/{obj.addr}/{obj.name}',#学校类型/学校地址/学校名
  5. }
  6. class School:
  7. def __init__(self,name,addr,type):
  8. self.name=name
  9. self.addr=addr
  10. self.type=type
  11. def __format__(self, format_spec): #format_spec = 'nat'
  12. if not format_spec or format_spec not in format_dict: #判断参数是否为空或者是否在format_dict字典里
  13. format_spec='nat' # 默认值为nat
  14. fmt=format_dict[format_spec] #'{obj.name}-{obj.addr}-{obj.type}'
  15. return fmt.format(obj=self) #'{obj.name}-{obj.addr}-{obj.type}'.format(obj=self)
  16. s1=School('oldboy1','北京','私立')
  17. print(format(s1,'nat')) #s1.__format__('nat')
  18. print(format(s1,'tna'))
  19. print(format(s1,'tan'))
  20. print(format(s1,'asfdasdffd')) # 字典不存在,走默认值。s1.__format__('nat')

执行输出:

oldboy1-北京-私立

私立:oldboy1:北京

私立/北京/oldboy1

oldboy1-北京-私立

重新回忆一下 if 判断的知识

if True:print(‘执行 if 中的代码’)

if False:print(‘不执行 if 中的代码’)

if True or False:print(‘or 两端的条件有一个为 True 就执行这个 if 中的代码’)

if not True or False:pass 结果为 False,不执行 if 代码

if not False or False:pass 结果为 True,执行 if 代码

if not format_spec or format_spec not in format_dict 有一个条件为 True,执行 if 代码

当数据为以下值时,分别是数字 0,空列表,空字符串,空字典,空元组,None

  1. 0 [] '' {} () None

表示 False,否则为 True

四、call

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

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

  1. class Teacher():
  2. def __call__(self):
  3. print(123)
  4. t = Teacher()
  5. t() # 对象名() 相当于调用类内置的__call__
  6. Teacher()() #效果同上

Teacher()() #效果同上

执行输出:

123

123

有些源码会出现类名()()这种的,它其实是调用了call方法。所以一定要注意!!!

call注释掉,再次执行

  1. class Teacher():
  2. pass
  3. # def __call__(self):
  4. # print(123)
  5. t = Teacher()
  6. t()

执行报错:

TypeError: ‘Teacher’ object is not callable

object 默认没有call方法

使用 callable 方法,判断对象是否可调用

  1. class Teacher():
  2. pass
  3. # def __call__(self):
  4. # print(123)
  5. t = Teacher()
  6. print(callable(Teacher))
  7. print(callable(t))

执行输出:

True

False

一个对象是否可调用 完全取决于这个对象对应的类是否实现了call

看 Teacher 类是否有call方法

  1. print('__call__' in Teacher.__dir__(Teacher))

执行输出:True

查看 callabble 方法的解释,有一句话

  1. Note that classes are callable (calling
  2. a class returns a new instance); instances are callable if their class has a __call__() method.

翻译解释:

注意类是可调用的(调用)

一类返回一个新实例);实例调用如果类有一个call ()方法。

print(callable(Teacher))的返回值是 True

call ()方法是针对对象的,而不是类。

python 一切皆对象

  1. class Teacher(object):
  2. def __call__(self):
  3. print(123)
  4. def call(self):
  5. print(456)
  6. t = Teacher()
  7. t.call() # 手动执行call方法

执行输出:456

五、eq

eq 定义了类的等号(==)行为

  1. class A:pass
  2. a = A()
  3. b = A()
  4. print(a)
  5. print(b)

执行输出:

<__main__.A object at 0x000001D32763BF28> <__main__.A object at 0x000001D327641320>

从结果上来看,内存地址是不一样的

判断是否相等

  1. class A:pass
  2. a = A()
  3. b = A()
  4. print(a == b)

执行输出:False

==实际是调用了 eq方法,它是判断内存地址,是否一致

自定义eq方法

  1. class A:
  2. def __eq__(self, other):
  3. return True
  4. a = A()
  5. b = A()
  6. a.name = 'alex' # 增加一个属性
  7. b.name = 'egon'
  8. print(a == b)

执行输出:True

== 是由eq的返回值来决定的

虽然明知道,a 和 b 是不可能相等的。但是类方法eq强制改变了结果,不管是什么,结果总是为 True

为了让eq方法更有意义,再改动一下。

  1. class A:
  2. def __eq__(self, other):
  3. if self.__dict__ == other.__dict__:
  4. return True
  5. else:
  6. return False
  7. a = A()
  8. b = A()
  9. a.name = 'alex' # 增加一个属性
  10. b.name = 'egon'
  11. print(a == b)

执行输出:False

六、del

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

注:此方法一般无须定义,因为 Python 是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给 Python 解释器来执行,所以,析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。

  1. class A:
  2. def __init__(self):
  3. pass
  4. def __del__(self):
  5. print('执行我啦')
  6. a = A()
  7. print('aaa')

执行输出:

aaa

执行我啦

当类执行完毕时,自动执行析构方法

删除一个对象

  1. class A:
  2. def __init__(self):
  3. pass
  4. def __del__(self):
  5. print('执行我啦')
  6. a = A()
  7. del a # 主动删除对象
  8. print('aaa')

执行输出:

执行我啦

aaa

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

看下面的例子

  1. class A:
  2. def __init__(self):
  3. self.f = open('文件','w') # 打开文件句柄
  4. # def __del__(self):
  5. # print('执行我啦')
  6. a = A()
  7. del a
  8. print('aaa')

执行输出:aaa

这段代码,有一个问题,文件打开了,但是文件句柄没有释放。那么就浪费内存了。

这个时候,需要在析构方法中,关闭文件句柄

  1. class A:
  2. def __init__(self):
  3. self.f = open('文件','w')
  4. def __del__(self):
  5. self.f.close() # 关闭文件句柄
  6. print('文件关闭了')
  7. a = A()
  8. del a

七、new

new方法是创建类实例的方法,它的调用是发生在init之前的

object 默认就有了

new的源码

  1. @staticmethod # known case of __new__
  2. def __new__(cls, *more): # known special case of object.__new__
  3. """ Create and return a new object. See help(type) for accurate signature. """
  4. pass

它是一个静态方法,没有 self,因为此刻还没有 self

new()面试必考

先有对象,才能初始化

new方法不需要写,object 自带

先来讲一个设计模式—>单例模式

如果面试中,只要 是问单例模式,就是问new

单例模式 就是一个类只能有一个实例

应用场景

1.当一个类,多次实例化时,每个实例都会占用资源,而且实例初始化会影响性能,这个时候就可以考虑使用单例模式,它给我们带来的好处是只有一个实例占用资源,并且只需初始化一次

2.当有同步需要的时候,可以通过一个实例来进行同步控制,比如对某个共享文件(如日志文件)的控制,对计数器的同步控制等,这种情况下由于只有一个实例,所以不用担心同步问题

看下面的代码:

  1. class A:pass
  2. a = A()
  3. b = A()
  4. print(a)
  5. print(b)

执行输出:

<__main__.A object at 0x00000299A8D9BF28> <__main__.A object at 0x00000299A8DA1320>

这不是单例模式,因为内存地址不一样

不是init的锅,是new的锅

new每次实例化,会创建一个新的内存地址

下面看一个真正的单例模式,使用new方法

  1. class B:
  2. __instance = None
  3. def __new__(cls, *args, **kwargs): # cls表示类
  4. if cls.__instance is None: # 判断类变量__instance是否为None
  5. obj = object.__new__(cls) # 创建一个实例对象
  6. cls.__instance = obj # 赋值
  7. return cls.__instance # 返回私有静态属性
  8. a = B()
  9. b = B()
  10. print(a)
  11. print(b)

执行输出:

<__main__.B object at 0x00000267F570BB00> <__main__.B object at 0x00000267F570BB00>

上面的结果,内存地址是一样的。

注意:new每次实例化,都会执行!!!

实例化时,先执行new,再执行init

第一次执行时,cls.__instance 是 None,创建一个对象

第二次执行时,cls.__instance 不是 None,返回私有静态属性

再添加几个属性

  1. class B:
  2. __instance = None
  3. def __new__(cls, *args, **kwargs):
  4. if cls.__instance is None:
  5. obj = object.__new__(cls)
  6. cls.__instance = obj
  7. return cls.__instance
  8. def __init__(self,name,age):
  9. self.name = name
  10. self.age = age<br>
  11. def func(self):
  12. print(self.name)
  13. a = B('alex',80) #实例化,传值
  14. b = B('egon',20) #实例化,覆盖值
  15. print(a)
  16. print(b)
  17. print(a.name)
  18. print(b.name)

执行输出:

<__main__.B object at 0x000002483F7DFF28> <__main__.B object at 0x000002483F7DFF28>

egon

egon

  1. ![](https://cdn.nlark.com/yuque/0/2020/png/1484428/1597656849660-a39331f6-965e-4d39-b12e-ac4277313875.png)

b 实例化时,a 对象的值指向就中断了。由于 a 和 b 共用一个内存空间,所以最终结果为 egon

八、item 系列

对象使用中括号的形式去操作

getitem 当访问不存在的属性时会调用该方法

setitem属性被赋值的时候都会调用该方法

delitem删除属性时调用该方法

  1. class Foo:
  2. def __init__(self,name):
  3. self.name=name
  4. def __getitem__(self,item):
  5. return self.__dict__[item]
  6. def __setitem__(self, key, value):
  7. self.__dict__[key]=value
  8. def __delitem__(self, key):
  9. print('del obj[key]时,我执行')
  10. self.__dict__.pop(key)
  11. f = Foo('alex')
  12. # f.name = ...
  13. print(f['name']) # f.__getitem__('name')
  14. f['age'] = 18 # 赋值
  15. print(f.age) # 自带的语法
  16. print(f['age']) # 修改
  17. f['age'] = 80
  18. print(f['age']) # 通过实现__getitem__得到的
  19. del f['age'] # 删除
  20. #print(f.age) # 删除

执行输出:

alex

18

18

80

del obj[key]时,我执行

想要用对象名.[名字]

必须实现getitem方法

getitem 只能有一个参数

setitem能接收 2 个参数,一个是等号左边,一个是等号右边的

delitem很少用

__delattr 不用实现,因为 object 自带就有

  1. class Foo:
  2. def __init__(self,name):
  3. self.name=name
  4. def __delattr__(self, item):
  5. print('del obj.key时,我执行')
  6. self.__dict__.pop(item)
  7. f = Foo('alex')
  8. del f.name #相当于执行了__delattr__
  9. # delattr(f,'name')

执行输出:

del obj.key 时,我执行

面试题:

有一个类Person,它有3个属性,分别是name,sex,age。

实例化100次,每个对象的内存地址是不一样的。

其中有2个对象,name和sex是一样的,age不同

那么如何,去掉这2个重复的对象?

答案:

  1. class Person:
  2. def __init__(self,name,age,sex):
  3. self.name = name
  4. self.age = age
  5. self.sex = sex
  6. def __hash__(self): # 实例化时,执行此方法
  7. return hash(self.name + self.sex) # 对name和sex做hash,因为有2个对象name和sex一样,age不同
  8. def __eq__(self, other): # 实例化时,执行此方法
  9. if self.name == other.name and self.sex == other.sex: # 判断每一个对象的name和sex是否相同
  10. return True
  11. p_lst1 = [] #定义一个列表
  12. #生成98个实例对象
  13. for i in range(98):
  14. p_lst1.append(Person('egon' + str(i),i,'male'))
  15. #手动增加2个重复的,name和sex值是一样的,age不同
  16. p_lst1.append(Person('egon50',200,'male'))
  17. p_lst1.append(Person('egon50',300,'male'))
  18. #查看p_lst1的长度
  19. print(len(p_lst1))
  20. #使用集合去重,查看p_lst1的长度
  21. print(len(set(p_lst1)))

执行输出:

100

98

注意:hasheq方法,必须自己定义,否则无法去重

明日默写:

  1. class B:
  2. __instance = None
  3. def __new__(cls, *args, **kwargs):
  4. if cls.__instance is None:
  5. obj = object.__new__(cls)
  6. cls.__instance = obj
  7. return cls.__instance
  8. def __init__(self,name,age):
  9. self.name = name
  10. self.age = age
  11. def func(self):
  12. print(self.name)
  13. a = B('alex',80)
  14. b = B('egon',20)
  15. print(a)
  16. print(b)
  17. print(a.name)
  18. print(b.name)