Fork me on GitHub

面向对象高级(魔法方法)

类型判断

issubclass

首先,我们先看issubclass() 这个内置函数可以帮我们判断x类是否是y类型的子类

class Base:
    pass


class Foo(Base):
    pass


class Bar(Foo):
    pass


print(issubclass(Bar, Foo)) #True
print(issubclass(Foo, Bar)) #False
print(issubclass(Bar, Base))#True
print(Bar.mro())
#[<class '__main__.Bar'>, <class '__main__.Foo'>,
<class '__main__.Base'>, <class 'object'>]
#严格按照此顺序确定继承关系

type

type(obj)表示查看obj是由哪个类创建的

class Foo():
    pass


obj = Foo()
print(type(obj))
# <class '__main__.Foo'>查看了类Foo的内存地址

isinstance

isinstance可以判断x是否是y的对象,isinstance可以判断该对象是否是家族体系中的(只能往上查找)

class Base:
    pass


class Foo(Base):
    pass


class Bar(Foo):
    pass


obj = Foo()
print(isinstance(obj, Foo)) #True
print(isinstance(obj, Base))#True
print(isinstance(obj, Bar)) #False Bar是Foo的子类查找顺序是往父类去的

#通过type不能顺着对象类的继承关系去进行判断,只能判断最近的一层关系类
print(type(obj) is Foo) #True
print(type(obj) is Base)#False

反射

1.什么是反射

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

2.Python面向对象中的反射

通过字符串的形式操作对象的相关属性。python中的一切事物都是对象(都可以使用反射)

3.反射的四个函数

​ 1.hasattr(obj,str) 判断obj中是否包含str成员

​ 2.getattr(obj,str) 从obj中获取str成员

​ 3.setattr(obj,str,value) 把obj中的str成员设置成value,这里的value可以是值,也可以是函数

​ 4.delattr(obj,str) 把obj中的str成员删除掉

4.反射函数的演示

class Student:
    school = 'oldboy'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def take_class(self):
        print(f'{self.name}正在上课')

    def flee_class(self):
        print(f'{self.name}正在逃课')


stu = Student('arther', 18)

# 检测是否含有某属性
print(hasattr(stu, 'name'))
print(hasattr(stu, 'take_class'))
# print(hasattr(stu,take_class)) #必须是字符串类型

# 获取对象的属性(该属性必须是对象已有的)
n = getattr(stu, 'name')
print(n)
func = getattr(stu, 'take_class')
func()

# 设置对象的属性
setattr(stu, 'gender', 'male')
setattr(stu, 'show_name', lambda self: self.name + 'sb')
print(stu.gender)
print(stu.show_name(stu))
print(stu.__dict__)

# 删除属性
delattr(stu, 'name')
delattr(stu, 'show_name')
print(stu.__dict__)

importlib

importlib是一个可以根据字符串的模块名实现动态导入模块的库。

目录结构

├── aaa.py
├── bbb.py
└── mypackage
    ├── __init__.py
    └── xxx.py

使用importlib动态导入模块:

bbb.py

import importlib

func = importlib.import_module('aaa')
print(func)
func.f1()

m = importlib.import_module('mypackage.xxx')
print(m.age)

_str_

改变对象的字符串显示,可以理解为使用print函数打印一个对象时,会自动调用对象的__str__方法

class Student:
    school = 'oldboy'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __str__(self):
        return self.name

stu = Student('arther', 18)
print(stu)  #会调用s1的__str__方法


_repr_

在python解释器环境下,会默认显示对象的repr表示

class Student:
    school = 'oldboy'

    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __repr__(self):
        return self.name


stu = Student('arther', 18)
stu
'''
repr或者交互式解释器--->obj.__repr__()
如果__str__没有被定义,那么就会使用__repr__来代替输出
注意:这俩方法的返回值必须是字符串,否则抛出异常
'''

_format_

date_dic = {
    'ymd': '{0.year}:{0.month}:{0.day}',
    'dmy': '{0.day}/{0.month}/{0.year}',
    'mdy': '{0.month}-{0.day}-{0.year}',
}


class Date:
    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day

    def __format__(self, format_spec):
        if not format_spec or format_spec not in date_dic:
          #没有传入格式或是传入的格式不在字典的key中
            format_spec = 'ymd'
        fmt = date_dic[format_spec]
        '{0.month}-{0.day}-{0.year}'
        return fmt.format(self)


d1 = Date(2016, 12, 29)
print(format(d1))
print('{:mdy}'.format(d1))

_del_

当对象在内存中被释放时,自动触发执行。
此方法一般无需定义,因为python是一门高级语言,程序员在使用时无需关心内存的分配和释放。

class A:
    def __del__(self):
        print('删除了')


a = A()
del a
>>>删除了

___dict__和_slots

python中的类,都会从object里继承一个__dict__属性,这个属性存放着类的属性和方法对应的键值对。一个类实例化之后,这个类的实例也有了这么一个__dict__属性。但是两者并不相同。

class A:
    some = 1

    def __init__(self, num):
        self.num = num


a = A(10)
print(A.__dict__)  # 类A的内置属性(魔法方法)
print(a.__dict__)  # {'num': 10}

由于每实例化一个类都要分配一个__dict__变量,容易浪费内存。因此在Python中有一个内置的__slots__属性。当一个类设置了__slots__属性后,这个类的__dict__属性就不存在了(同理,该类的实例也不存在__dict__属性),如此一来,设置了__slots__属性的类的属性,只能是预先设定好的。

class A:
    __slots__ = ['name', 'age']
    some = 1

a = A()
a.name = 'arther'
a.age = 18
print(a.some)
print(a.name)
print(a.__slots__) #['name', 'age']

注意:

定义了_slots_____后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该只在那些经常被使用到的用作数据结构的类上定义_slots,比如在程序中需要创建某个类的几百万个实例对象 。
关于____slots____的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用____slots____可以达到这样的目的,但是这个并不是它的初衷。它更多的是用来作为一个内存优化工具。

__setattr__,__delattr__,__getattr___

class Foo:
    x = 1

    def __init__(self, y):
        self.y = y

    def __getattr__(self, item):
        print('from getattr:你找的属性不在')

    def __setattr__(self, key, value):
        print('from setattr')
        # 需要在这里进行对对象属性的赋值操作
        # self.key = value  # 相当于又触发了__setattr__无限递归
        self.__dict__[key] = value  # 直接添加到对象属性的字典,可以避免

    def __delattr__(self, item):
        print('from delattr')
        # del self.item #与上例相同,无限递归了
        self.__dict__.pop(item)


# __setattr__添加/修改属性会触发它的执行
f1 = Foo(10)
print(f1.__dict__)
>>>from setattr,{}:重写了__getattr__所以根本没有进行赋值操作
f1.z = 3
print(f1.__dict__)

# __delattr__删除属性的时候会触发
#与del区别:一个是删除对象触发,一个是删除对象的属性触发
f1.__dict__['a'] = 3  # 我们可以直接修改属性字典,来完成添加/修改属性的操作
del f1.a
print(f1.__dict__)   
>>>from delattr

# __getattr__只有在调用属性且属性不存在的时候才会触发
f1.xxxxxx   
>>>from getattr:你找的属性不在

__item__系列

主要是以obj[key]=values的形式进行调用所产生的方法。

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

    def __getitem__(self, item):
        return self.__dict__[item]

    def __setitem__(self, key, value):
        print('obj[key]=lqz赋值时,执行我')
        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__)
#obj[key]=lqz赋值时,调用__setitem
f1['age'] = 18

f1.hobby = '泡妞'
#del obj.key时,调用__delattr__
del f1.hobby

#del obj['age']时,调用__delitem__
del f1['age']
f1['name'] = 'lqz'
print(f1.__dict__)

#打印obj['name']时,调用__getitem__
print(f1['name'])

#也可以使用
print(f1.name)

_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)
      #调用父类__new__的方法造一个空对象并且return给__init__给该对象进行一个实例化

    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)

输出:

对于__init__已有的属性:
调用__new__,创建类实例
调用__init__,初始化实例
调用__setattr__,将对象的属性以key:value的形式保存在对象的属性字典中

对于__init__没有的属性(新增属性):
由于对象已经进行完成实例化,那么不用造空对象,由于新增属性是__init__没有的属性。
所以略过了__now__与__init__直接通过__setattr__将对象的属性以key:value的形式保存在对象的属性字典中。

示范:

class Foo():
    # def __new__(cls,x,y): #只能匹配__init__中的key,所以对象新增属性无需经过此方法
    #     if x<10:
    #         return object.__new__(cls)
            
    def __init__(self, x):
        print('初始化对象属性')
        self.x = x
        print(self.__dict__, 'from init')

    def __setattr__(self, key, value):
        print('添加对象属性')
        self.__dict__[key] = value
        print(self.__dict__, 'from setattr')

f = Foo(1)
>>>初始化对象属性
>>>添加对象属性
#初始化对象后执行完__setattr__将对象属性保存到字典中后再回来继续执行。
>>>{'x': 1} from setattr
>>>{'x': 1} from init
f.y = 2
#直接略过了__new__和__init__
>>>添加对象新属性
>>>{'x': 1, 'y': 2}

__new__方法在类定义中不是必须写的,如果没定义的话默认会调用object.__new__去创建一个对象(因为创建类的时候默认继承的就是object)。如果我们在类中定义了__new__方法,就是重写了默认的__new__方法,我们可以借此自定义创建对象的行为。

例子1:

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

    def __new__(cls, y):
        if y > 5:   
            return object.__new__(cls)
          #赋值属性的值大于5则调用父类方法造一个空对象并返回进行实例化
        else:
            return None

    def __str__(self):
        return f'{self.__dict__}'

a = Foo(1)
print(a)
>>>None
b = Foo(10)
>>>{'y': 10}

例子2(单例模式):

单例模式是指在内存中只会创建且仅创建一次对象的设计模式。在程序中多次使用同一个对象且作用相同时,为了防止频繁地创建对象使得内存飙升,单例模式可以让程序仅在内存中创建一个对象,让所有需要调用的地方都共享这一单例对象,即在全局中不论创建多少个“对象”都是引用的同一个对象的内存地址。

class Singleton:
    # 重写__new__方法,实现每一次实例化的时候,返回同一个instance对象
    def __new__(cls, *args, **kw):
    		#是否有对象存在
        if not hasattr(cls, '_instance'):
        		#没有则创建一个空对象
            cls._instance = super().__new__(Singleton)
      #不论对象是否存在都将返回被初始化,如果对象存在则该对象属性被新对象初始化属性覆盖(同样的key)
      #如果对象不存在,直接返回空对象进行初始化
        return cls._instance

    def __init__(self, name, age):
        self.name = name
        self.age = age


s1 = Singleton('张三', 24)
s2 = Singleton('李四', 20)
print(s1, s2)  # 这两实例都一样
print(s1.name, s2.name)
>>>李四 李四   #只能存在一个对象的内存地址,s1,s2相当于两个变量名引用同一个内存地址
							#所以旧对象的属性会被新对象的属性覆盖

_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可以调用

_doc_

定义类的描述信息。注意该信息无法被继承。

class A:
    """我是A类的描述信息"""
    pass

print(A.__doc__)


__iter__和_next_

如果一个对象拥有了__iter__和__next__方法,那这个对象就是可迭代对象

class A:
    def __init__(self, start, stop=None):
        if not stop:
            start, stop = 0, start
        self.start = start
        self.stop = stop

    def __iter__(self):
        return self

    def __next__(self):
      #每次一迭代start都要小于stop才可输出
        if self.start >= self.stop:
            raise StopIteration
        n = self.start #不可变
        self.start += 1 #提前加好为下一次迭代做准备
        return n


for i in A(1, 5):
    print(i)

for j in A(5):
    print(j)

__enter__和__exit__

一个对象如果实现了__enter__和__exit__方法,那么这个对象就支持上下文管理协议,即with语句

class A:
    def __enter__(self):
        print('进入with语句块时执行此方法,此方法如果有返回值会赋值给as声明的变量')
        return 'oo'

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('退出with代码块时执行此方法')
        print('1', exc_type)
        print('2', exc_val)
        print('3', exc_tb)

with A() as f:
    print('进入with语句块')
    # with语句中代码块出现异常,则with后的代码都无法执行。
    # raise AttributeError('sb')
    print(f) #f打印出oo
print('嘿嘿嘿')

上下文管理协议适用于那些进入和退出之后自动执行一些代码的场景,比如文件、网络连接、数据库连接或使用锁的编码场景等。

_len_

拥有__len__方法的对象支持len(obj)操作。

class A:
    def __init__(self):
        self.x = 1
        self.y = 2

    def __len__(self):
        return len(self.__dict__)

a = A()
print(len(a))

_hash_

拥有__hash__方法的对象支持hash(obj)操作。

class A:
    def __init__(self):
        self.x = 1
        self.x = 2

    def __hash__(self):
        return hash(str(self.x) + str(self.x))

a = A()
print(hash(a))

_eq_

拥有__eq__方法的对象支持相等的比较操作

class A:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __eq__(self,obj):
        # 打印出比较的第二个对象的x值
        print(obj.x)
        if self.x +self.y == obj.x+obj.y:
            return True
        else:
            return False

a = A(1,2)
b = A(2,1)
print(a == b)

posted @ 2020-09-17 21:12  artherwan  阅读(176)  评论(0编辑  收藏  举报