day28-面相对象的特殊成员、内置函数

时间:2024-11-06 13:36:26

1、 isinstance与issubclass

1.1、isinstance(obj,cls)

检查obj是否是类cls的对象,或者是类cls的子类的对象

class A: pass
class B(A): pass abj = B()
print(isinstance(abj,B)) #True
print(isinstance(abj,A)) #True

1.2、issubclass(sub, super)

检查sub类是否是super类的子类,或者是super子类的子类

class A: pass
class B(A): pass print(issubclass(B,A)) #True 这里2个参数都是类名,不是对象名

2、内置函数

__len__ 返回类的__dict__字典中属性的个数,通过len(obj)调用
__hash__ 返回一个哈希值,通过hash(obj)调用
__str__ 返回一个字符串,直接打印obj时调用
__repr__ 返回一个字符串,直接打印repr(obj)时调用
__call__ (重要) 对象后面加括号,触发执行
__eq__ 判断属性是否相等,设置返回值,通过打印obj1 == obj2调用
__del__ 析构方法,当对象在内存中被释放时,自动触发执行。
__new__ (重要)
__item__系列 (重要) 对实例化对象进行类似字典的操作,有3种
__getitem__
__setitem__
__delitem__

__attr__系列(重要),对实例化对象进行对象操作,有3种
__getattr__
__setattr__
__delattr__

__iter__ 实现迭代器
__next__ 从迭代器中取值

2.1 __len__

class A:
def __init__(self):
self.a = 1
self.b = 2 def __len__(self):
return len(self.__dict__) a = A()
print(len(a))
# 等同于下面的效果
class A:
def __init__(self):
self.a = 1
self.b = 2 def len(self):
return len(self.__dict__)
a = A()
print(a.len())

2.2、__hash__

class A:
def __init__(self):
self.a = 1
self.b = 2 def __hash__(self):
return hash(str(self.a))
a = A()
print(hash(a))
# 737862024 将self.a的值转换成字符串1,再进行hash 等同于下面的效果
class A:
def __init__(self):
self.a = 1
self.b = 2
def hash(self):
return str(self.a)
a = A()
print(hash(a.hash()))

2.3、__str__
如果一个类中定义了__str__方法,那么在打印对象时,默认输出该方法的返回值。

class A:
def __init__(self):
self.a = 1
self.b = 2
def __str__(self):
return ''
a = A()
print(a)
#

2.4、__repr__

如果一个类中定义了__repr__方法,那么在repr(对象) 时,默认输出该方法的返回值。

class A:
def __init__(self):
self.a = 1
self.b = 2
def __repr__(self):
return ''
a = A()
print(repr(a))
#

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

class Foo:
def __init__(self):
print('in __init__')
def __call__(self, *args, **kwargs):
print('in __call__')
obj = Foo() #执行 __init__
# in __init__
obj() # 执行 __call__
# in __call__

2.6、__eq__ 

class Foo:
def __init__(self, a, b):
self.a = a
self.b = b
def __eq__(self, obj):
if self.a == obj.a and self.b == obj.b: #判断条件可自行设置
return True #返回值可自行定义
obj1 = Foo(1,2)
obj2 = Foo(1,2)
obj3 = Foo(2,3)
print(obj1 == obj2)
# True
print(obj1 == obj3)
# None

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

class Animal(object):
def __init__(self,name):
self.__name = name def __del__(self):
print('啊。。。')
dog = Animal('旺财')
print('---1---') ---1---
啊。。。

虽然没有调用__del__()方法,那是谁调用的呢?
python解释器如果检测到一个对象没有任何用处了,那么就把这个对象kill掉。

2.8、__new__  (重要)

class A:
def __init__(self):
print('in __init__') def __new__(cls,*args, **kwargs):
print('in __new__') obj = A()
print(obj)
# 结果:
# in __new__
# None
# 类A里面的__new__方法中并没有创建对象,所以对象obj并不存在 class A:
def __init__(self, name):
print('in __init__')
self.name = name def __new__(cls, *args, **kwargs):
print('in __new__')
return object.__new__(cls) # 调用object类中的__new__方法 obj = A('Tom')
# in __new__
# in __init__
print(obj.name)
# Tom
可以看出在实例化对象时先执行了__new__方法,再执行__init__方法

单例模式

定义一个类A,当实例化对象obj1和obj2时,会在内存中分别存储2个对象的空间,通过打印对象可以看出内存地址不同

class A:
__instance = None
def __init__(self, name):
self.name = name obj1 = A('Tom')
print(obj1)
# <__main__.A object at 0x00622490>
obj2 = A('Mike')
print(obj2)
# <__main__.A object at 0x00622910>

那么是否可以创建多个实例化对象时共用同一个内存空间,而不让每次创建实例化对象时都占用一个内存空间,从而节省内存呢?

我们可以在创建多个实例化对象时判断是否已经有实例化对象了,如果有就沿用第1个对象的内存空间,而不是重新创建新的内存空间。

class A:
__instance = None
def __init__(self, name):
self.name = name
def __new__(cls, *args, **kwargs):
if cls.__instance is None:
cls.__instance = object.__new__(cls)
return cls.__instance obj1 = A('Tom')
print(obj1)
# <__main__.A object at 0x01E901D0>
obj2 = A('Mike')
print(obj2)
# <__main__.A object at 0x01E901D0>
obj3 = A('Jack')
print(obj3)
# <__main__.A object at 0x01E901D0>
# 可以看到3个实例化对象的内存空间是相同的,其实都是第1个实例化对象的地址

单例模式具体分析

单例模式是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源。如果希望在系统中某个类的对象只能存在一个,单例模式是最好的解决方案。

【采用单例模式动机、原因】

对于系统中的某些类来说,只有一个实例很重要,例如,一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务;一个系统只能有一个窗口管理器或文件系统;一个系统只能有一个计时工具或ID(序号)生成器。如在Windows中就只能打开一个任务管理器。如果不使用机制对窗口对象进行唯一化,将弹出多个窗口,如果这些窗口显示的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显示的内容不一致,则意味着在某一瞬间系统有多个状态,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。因此有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。一个更好的解决办法是让类自身负责保存它的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。这就是单例模式的模式动机。

【单例模式优缺点】
【优点】
一、实例控制
单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
二、灵活性
因为类控制了实例化过程,所以类可以灵活更改实例化过程。

【缺点】
一、开销
虽然数量很少,但如果每次对象请求引用时都要检查是否存在类的实例,将仍然需要一些开销。可以通过使用静态初始化解决此问题。
二、可能的开发混淆
使用单例对象(尤其在类库中定义的对象)时,开发人员必须记住自己不能使用new关键字实例化对象。因为可能无法访问库源代码,因此应用程序开发人员可能会意外发现自己无法直接实例化此类。
三、对象生存期
不能解决删除单个对象的问题。在提供内存管理的语言中(例如基于.NET Framework的语言),只有单例类能够导致实例被取消分配,因为它包含对该实例的私有引用。在某些语言中(如 C++),其他类可以删除对象实例,但这样会导致单例类中出现悬浮引用

2.9、__item__系列 (重要)

将实例化对象当作一个字典来进行操作

1)__getitem__:返回__dict__字典中key为item的值,通过对象['key']来调用

class Foo1:
def __init__(self, name, age):
self.name = name
self.age = age
def __getitem__(self, item):
return self.__dict__[item] obj1 = Foo1('Tom',20)
print(obj1['name'])
# Tom

2)__setitem__ : 将__dict__字典中key的值设置为vlaue,通过对象['key'] = vlaue来调用

class Foo2:
def __init__(self, name, age):
self.name = name
self.age = age
def __setitem__(self, key, value):
self.__dict__[key] = value
obj2 = Foo2('Mike', 25)
obj2['sex'] = 'man'
print(obj2.__dict__)
# {'name': 'Mike', 'age': 25, 'sex': 'man'}

3)__delitem__: 将__dict__字典中key的值删除,通过del 对象.key来调用

class Foo3:
def __init__(self, name, age):
self.name = name
self.age = age
def __delitem__(self, key):
self.__dict__.pop(key)
obj3 = Foo3('Jack', 28)
del obj3['age']
print(obj3.__dict__)
# {'name': 'Jack'}

其实这3种item方法都可以通过下面的代码实现,只是item方法在操作上可以将对象当作一个字典来实现,对于外面的调用者来说只需要知道这是一个字典就可以了,不需要知道这是一个对象,这个对象的方法是怎么调用的。

class Foo4:
def __init__(self, name, age):
self.name = name
self.age = age
def get(self):
print(self.__dict__)
def set(self, key, value):
self.__dict__[key] = value
def delete(self,key):
self.__dict__.pop(key)
obj4 = Foo4('Tom', 20)
obj4.get()
# {'name': 'Tom', 'age': 20}
obj4.set('sex', 'man')
obj4.get()
# {'name': 'Tom', 'age': 20, 'sex': 'man'}
obj4.delete('name')
obj4.get()
# {'age': 20, 'sex': 'man'}

2.10、__attr__系列 (重要)

将实例化对象当作一个对象来进行操作

1)__getattr__:返回__dict__字典中key为item的值,通过对象.'key'来调用

class Foo1:
def __init__(self, name, age):
self.name = name
self.age = age
def __getattr__(self, item):
return self.__dict__[item] obj1 = Foo1('Tom',20)
print(obj1.name)
# Tom

2)__setattr__ : 将__dict__字典中key的值设置为vlaue,通过对象.'key' = vlaue来调用

class Foo2:
def __init__(self, name, age):
self.name = name
self.age = age
def __setitem__(self, key, value):
self.__dict__[key] = value
obj2 = Foo2('Mike', 25)
obj2.sex = 'man'
print(obj2.__dict__)
# {'name': 'Mike', 'age': 25, 'sex': 'man'}

3)__delattr__: 将__dict__字典中key的值删除,通过del 对象.key来调用

class Foo3:
def __init__(self, name, age):
self.name = name
self.age = age
def __delattr__(self, key):
self.__dict__.pop(key)
obj3 = Foo3('Jack', 28)
del obj3.age
print(obj3.__dict__)
# {'name': 'Jack'}

2.11、__iter__和__next__实现迭代器

例子1

class Foo:
def __init__(self,start):
self.start = start def __iter__(self):
return self def __next__(self):
self.start+=1 #每次调用,将值加1
return self.start f1 = Foo(10)
print(f1.__next__())
11
print(next(f1))
12 for i in f1:
if i > 20:
break
print(i)
11
12
13
14
15
16
17
18
19
20

例子2:实现斐波那契

class Fib:
def __init__(self):
self._a = 0
self._b = 1 def __iter__(self):
return self def __next__(self):
self._a, self._b = self._b, self._a + self._b
return self._a fib = Fib() for i in fib:
if i > 10:
break
print(i) 1
1
2
3
5
8

3、二次加工标准类型

包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制自己的数据类型,新增/改写方法,这就用到了继承/派生的知识。

例子:

1、定义一个列表,增加一个需求,该列表可以取出下标在中间的值

自定义一个List类,继承内置list类,并增加新的方法get_middle
class List(list):
  def __init__(self, item):
  super().__init__(item)
  self.item = item   def get_middle(self):
    mid_index = len(self.item)//2
  return self.item[mid_index] l1 = List([1,2,3,4,5])
print(l1)
#[1, 2, 3, 4, 5] print(l1.get_middle())
# l1.append(6)
print(l1)
#[1, 2, 3, 4, 5, 6]

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

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

class List:
def __init__(self,seq,permission=False):
self.seq=seq
self.permission=permission def get_middle(self):
    mid_index = len(self.seq)//2
  return self.seq[mid_index] def clear(self):
if not self.permission:
raise PermissionError('not allow the operation')
self.seq.clear() def __getattr__(self, item):
return getattr(self.seq,item) def __str__(self):
return str(self.seq)
l=List([1,2,3])
l.clear() #此时没有权限,抛出异常
# Traceback (most recent call last):
# File "C:\Users\Administrator\Desktop\test.py", line 16, in <module>
# l.clear() #此时没有权限,抛出异常
# File "C:\Users\Administrator\Desktop\test.py", line 7, in clear
# raise PermissionError('not allow the operation')
# PermissionError: not allow the operation l.permission=True
print(l)
#[1, 2, 3]
l.clear()
print(l)
#[] #基于授权,获得insert方法
l.insert(0,-123)
print(l)
#[-123]

例子2:对文件进行写操作时,每一行前面添加上时间标签

import time

class Open:
def __init__(self, filename, mode='r',encoding='utf-8'):
self.file = open(filename, mode, encoding=encoding) def write(self,str):
time_str = time.strftime('%Y-%m-%d %X')
self.file.write('%s %s'%(time_str, str)) def __getattr__(self,item):
return getattr(self.file, item) f1 = Open('a1.txt','w+')
f1.write('111111\n')
f1.write('222222\n')
f1.seek(0)
print(f1.read())

2020-04-07 19:05:30 111111
2020-04-07 19:05:30 222222