面向对象---进阶

一、类型判断

isinstance(obj,cls)检查obj是否 是类 cls 的对象

class Base:
    pass

class Foo(Base):
    pass

class Bar(Foo):
    pass

print(isinstance(Foo(), Foo))  # True
print(isinstance(Foo(), Base))  # True
print(isinstance(Foo(), Bar))   # False

issubclass(sub, super)检查sub类是否 是 super 类的派生类

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

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

class Foo:
    pass

obj = Foo()
print(obj, type(obj))  # 查看obj的类

二、反射

1、 什么是反射

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

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

反射的四个函数

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成员删除掉。
class BlackMedium:
    feature='Ugly'
    def __init__(self,name,addr):
        self.name=name
        self.addr=addr

    def sell_house(self):
        print('%s 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼' %self.name)
    def rent_house(self):
        print('%s 黑中介租房子啦,傻逼才租呢' %self.name)

b1=BlackMedium('万成置地','回龙观天露园')

#hasattr  检测是否含有某属性
print(hasattr(b1,"feature"))    #Ture
print(hasattr(b1,"name"))   #Ture
print(hasattr(b1,"sell_house"))     #Ture

#getattr 获取属性
print(getattr(b1,"name",None))  #万成置地
print(getattr(b1,"feature",None))  #Ugly
getattr(b1,"sell_house",None)()  #sell_house对象,加括号执行结果——万成置地 黑中介卖房子啦,傻逼才买呢,但是谁能证明自己不傻逼

#setattr 设置属性
setattr(b1,"feature",123)
print(b1.feature)    #123

setattr(b1,"sell_house",123)
print(b1.sell_house)    #123

setattr(b1,"name",123)
print(b1.name)    #123


#delattr  删除属性
delattr(b1,"name")
# delattr(b1,"show_name")   #不存在,则报错
print(b1.__dict__)    #{'addr': '回龙观天露园'}
四种方式使用

3 为什么用反射之反射的好处

好处一:实现可插拔机制

事先定义好接口,接口只有在被完成后才会真正执行,这实现了即插即用,这其实是一种‘后期绑定’,什么意思?即你可以事先把主要的逻辑写好(只定义接口),然后后期再去实现接口的功能

好处二:动态导入模块

 

importlib

importlib是一个可以根据字符串的模块名实现动态导入模块的库。(基于反射当前模块成员)

举个例子:

目录结构:

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

使用importlib动态导入模块:aaa.py 和  xxx.py

import importlib

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

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

__setattr__ 、 __delattr__ 、__getattr__

PS: 对象 属性  的时候才会触发

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 #这就无限递归了,你好好想想
        # 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__) # 因为你重写了__setattr__,凡是赋值操作都会触发它的运行,你啥都没写,就是根本没赋值,除非你直接操作属性字典,否则永远无法赋值
f1.z=3    #赋值 对象 . 属性 = 3
print(f1.__dict__)

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

#__getattr__只有在使用点调用属性且属性不存在的时候才会触发
f1.xxxxxx   # 获取  对象 . 属性

__str__  和__repr__的区别:

#__str__方法: 改变对象的字符串显示。
class Student: 
    def __init__(self, name, age):
        self.name = name
        self.age = age
        
    # 定义对象的字符串表示
    def __str__(self):               #使用print函数打印的时候自动触发
        return self.name
    
s1 = Student('张三', 24)
print(s1)  # 会调用s1的__str__方法
#__repr__方法: 在python解释器环境下,会默认显示对象的repr表示。
class Student:
 def __init__(self, name, age):
     self.name = name
     self.age = age
 def __repr__(self):
     return self.name

s1 = Student('张三', 24)
print(s1)

总结:

改变对象的字符串显示__str__,__repr__

str函数或者print函数调用的是obj.__str__()

repr函数或者交互式解释器调用的是obj.__repr__()

如果__str__没有被定义,那么就会使用__repr__来代替输出。

注意:
__str__和__repr__方法的返回值都必须是字符串,否则抛出异常

 

 __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

__format__

自定制格式化字符串__format__

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

    __format_dict = {
        'n-a': '名字是:{obj.name}-年龄是:{obj.age}',  # 名字是:lqz-年龄是:18
        'n:a': '名字是:{obj.name}:年龄是:{obj.age}',  # 名字是:lqz:年龄是:18
        'n/a': '名字是:{obj.name}/年龄是:{obj.age}',  # 名字是:/年龄是:18
    }

    def __format__(self, format_spec):
        if not format_spec or format_spec not in self.__format_dict:
            format_spec = 'n-a'
        fmt = self.__format_dict[format_spec]
        print(fmt) #{obj.name}:{obj.age}
        return fmt.format(obj=self)

s1 = Student('lqz', 24)
ret = format(s1, 'n/a')
print(ret)  # lqz/24

__dict__和__slots__

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

class A:
    some = 1

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

a = A(3)
print(a.__dict__)  # {'num': 3}
a.age = 10
print(a.__dict__)  # {'num': 3, '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__可以达到这样的目的,但是这个并不是它的初衷。它更多的是用来作为一个内存优化工具。

 __setitem__,__getitem,__delitem__(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]=lqz赋值时,执行我')
        self.__dict__[key] = value

    def __delitem__(self, key):
        print('del obj[key]时,执行我')  #按字典 方式del时执行
        self.__dict__.pop(key)

    def __delattr__(self, item):
        print('del obj.key时,执行我')   #按 . 语法del时执行
        self.__dict__.pop(item)


f1 = Foo('rb')
print(f1.__dict__)
f1['age'] = 18
f1.hobby = '压马路'
del f1.hobby
del f1['age']
f1['name'] = 'TF'
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__方法在类定义中不是必须写的,如果没定义的话默认会调用object.__new__去创建一个对象(因为创建类的时候默认继承的就是object)。

如果我们在类中定义了__new__方法,就是重写了默认的__new__方法,我们可以借此自定义创建对象的行为。

举个例子:

__new__方法来实现单例模式

class Singleton:
    # 重写__new__方法,实现每一次实例化的时候,返回同一个instance对象
    def __new__(cls, *args, **kwargs):
        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('李四', 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可以调用

注意: 

__new__、__init__、__call__等方法都不是必须写的。

__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):
        if self.start >= self.stop:
            raise StopIteration
        n = self.start
        self.start += 1
        return n

a = A(1, 5)
from collections import Iterator
print(isinstance(a, Iterator))

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

for i in A(5):
    print(i)
aaa=A(1)
print(next(aaa))
print(next(aaa)) #当i=4的时候停止,当时如果继续next就抛异常
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

f1=Fib()

print(f1.__next__())
print(next(f1))
print(next(f1))

for i in f1:
    if i > 100:
        break
    print('%s ' %i,end='')
斐波那契数列

__enter__和__exit__

我们知道在操作文件对象的时候可以这么写

with open('a.txt') as f:
  '代码块'

上述叫做上下文管理协议,即with语句,为了让一个对象兼容with语句,必须在这个对象的类中声明__enter__和__exit__方法

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('嘿嘿嘿')

__exit__()中的三个参数分别代表异常类型,异常值和追溯信息,with语句中代码块出现异常,则with后的代码都无法执行

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

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')
        print(exc_type)
        print(exc_val)
        print(exc_tb)



with Open('a.txt') as f:
    print('=====>执行代码块')
    raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->不会执行
View Code

如果__exit()返回值为True,那么异常会被清空,就好像啥都没发生一样,with后的语句正常执行

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

    def __enter__(self):
        print('出现with语句,对象的__enter__被触发,有返回值则赋值给as声明的变量')

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('with中代码块执行完毕时执行我啊')
        print(exc_type)
        print(exc_val)
        print(exc_tb)
        return True



with Open('a.txt') as f:
    print('=====>执行代码块')
    raise AttributeError('***着火啦,救火啊***')
print('0'*100) #------------------------------->会执行
View Code

用途或者说好处

1.使用with语句的目的就是把代码块放入with中执行,with结束后,自动完成清理工作,无须手动干预

2.在需要管理一些资源比如文件,网络连接和锁的编程环境中,可以在__exit__中定制自动释放资源的机制,你无须再去关系这个问题,这将大有用处

__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 @ 2019-02-23 20:20  萤huo虫  阅读(225)  评论(0编辑  收藏  举报