面向对象深度探索day2
类型判断(2)
__del__
析构方法,当对象在内存中被释放时,自动触发执行。
注:此方法一般无须定义,因为Python是一门高级语言,程序员在使用时无需关心内存的分配和释放,因为此工作都是交给Python解释器来执行,所以析构函数的调用是由解释器在进行垃圾回收时自动触发执行的。
class A: def __del__(self): print('删除了...') a = A() print(a) # <__main__.A object at 0x10164fb00> del a # 删除了... print(a) # NameError: name 'a' is not defined
__dict__和__slots__
Python中的类,都会从object里继承一个__dict__属性,这个属性中存放着类的属性和方法对应的键值对。一个类实例化之后,这个类的实例也具有这么一个__dict__属性。但是二者并不相同。
class A: some = 1 def __init__(self, num): self.num = num a = A(10) print(a.__dict__) # {'num': 10} a.age = 10 print(a.__dict__) # {'num': 10, 'age': 10}
从上面的例子可以看出来,实例只保存实例的属性和方法,类的属性和方法它是不保存的。正是由于类和实例有__dict__属性,所以类和实例可以在运行过程动态添加属性和方法。
但是由于每实例化一个类都要分配一个__dict__变量,容易浪费内存。因此在Python中有一个内置的__slots__属性。当一个类设置了__slots__属性后,这个类的__dict__属性就不存在了(同理,该类的实例也不存在__dict__属性),如此一来,设置了__slots__属性的类的属性,只能是预先设定好的。
当你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的小型数组来构建的,而不是为每个实例都定义一个__dict__字典,在__slots__中列出的属性名在内部被映射到这个数组的特定索引上。使用__slots__带来的副作用是我们没有办法给实例添加任何新的属性了。
注意:尽管__slots__看起来是个非常有用的特性,但是除非你十分确切的知道要使用它,否则尽量不要使用它。比如定义了__slots__属性的类就不支持多继承。__slots__通常都是作为一种优化工具来使用。--摘自《Python Cookbook》8.4
class A: __slots__ = ['name', 'age'] a1 = A() # print(a1.__dict__) # AttributeError: 'A' object has no attribute '__dict__' a1.name = '晶晶' a1.age = 24 # a1.hobby = '打劫' # AttributeError: 'A' object has no attribute 'hobby' print(a1.__slots__)
注意事项:
__slots__的很多特性都依赖于普通的基于字典的实现。
另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义__slots__,比如在程序中需要创建某个类的几百万个实例对象 。
关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。它更多的是用来作为一个内存优化工具。
__item__系列
class Foo: def __init__(self, name): self.name = name def __getitem__(self, item): print(self.__dict__[item]) def __setitem__(self, key, value): print('obj[key]=daduo赋值时,执行我') 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') print(f1.__dict__) f1['age'] = 18 f1.hobby = '打劫' del f1.hobby del f1['age'] f1['name'] = 'lqz' print(f1.__dict__)
__init__(重点)
使用Python写面向对象的代码的时候我们都会习惯性写一个 __init__ 方法,__init__ 方法通常用在初始化一个类实例的时候。例如:
class Person: def __init__(self, name, age): self.name = name self.age = age def __str__(self): return '<Person: {}({})>'.format(self.name, self.age) p1 = Person('晶晶', 24) print(p1)
上面是__init__最普通的用法了。但是__init__其实不是实例化一个类的时候第一个被调用的方法。当使用 Persion(name, age) 来实例化一个类时,最先被调用的方法其实是 __new__ 方法。
__new__
其实__init__是在类实例被创建之后调用的,它完成的是类实例的初始化操作,而 __new__方法正是创建这个类实例的方法
class Person: def __new__(cls, *args, **kwargs): print('调用__new__,创建类实例') return super().__new__(Person) def __init__(self, name, age): print('调用__init__,初始化实例') self.name = name self.age = age def __str__(self): return '<Person: {}({})>'.format(self.name, self.age) p1 = Person('晶晶', 24) print(p1)
输出:
调用__new__,创建类实例
调用__init__,初始化实例
<Person: 晶晶(24)>
__new__方法在类定义中不是必须写的,如果没定义的话默认会调用object.__new__去创建一个对象(因为创建类的时候默认继承的就是object)。
如果我们在类中定义了__new__方法,就是重写了默认的__new__方法,我们可以借此自定义创建对象的行为。
举个例子:
重写类的__new__方法来实现单例模式。
class Singleton: # 重写__new__方法,实现每一次实例化的时候,返回同一个instance对象 def __new__(cls, *args, **kw): if not hasattr(cls, '_instance'): cls._instance = super().__new__(Singleton) return cls._instance def __init__(self, name, age): self.name = name self.age = age s1 = Singleton('晶晶', 24) s2 = Singleton('kk', 20) print(s1, s2) # 这两实例都一样 print(s1.name, s2.name)
__call__
__call__ 方法的执行是由对象后加括号触发的,即:对象()。拥有此方法的对象可以像函数一样被调用。
class Person: def __init__(self, name, age): self.name = name self.age = age def __call__(self, *args, **kwargs): print('调用对象的__call__方法') a = Person('晶晶', 24) # 类Person可调用 a() # 对象a可以调用

浙公网安备 33010602011771号