Python基础(十)-面向对象三

一、isinstance(obj,cls)和issubclass(sub,super)

1.1、isinstance(obj,cls) 

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

class Foo(object):
    pass

obj = Foo()
print(isinstance(obj,Foo))  #True

1.2、issubclass(sub, super)

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

class Foo(object):
    pass

class Bar(Foo):
    pass

print(issubclass(Bar,Foo))  #True

二、反射

四个可以实现自省的函数,适用于类和对象(一切皆对象,类本身也是一个对象)

2.1、hasattr(object,name)

判断object中有没有一个name字符串对应的方法或属性

2.2、getattr(object, name, default=None)

def getattr(object, name, default=None): # known special case of getattr
    """
    getattr(object, name[, default]) -> value

    Get a named attribute from an object; getattr(x, 'y') is equivalent to x.y.
    When a default argument is given, it is returned when the attribute doesn't
    exist; without it, an exception is raised in that case.
    """
    pass

getattr(object, name, default=None)

2.3、setattr(x, y, v)

设置属性

def setattr(x, y, v): # real signature unknown; restored from __doc__
    """
    Sets the named attribute on the given object to the specified value.

    setattr(x, 'y', v) is equivalent to ``x.y = v''
    """
    pass

setattr(x, y, v)

2.4、delattr(x, y)

删除属性

def delattr(x, y): # real signature unknown; restored from __doc__
    """
    Deletes the named attribute from the given object.

    delattr(x, 'y') is equivalent to ``del x.y''
    """
    pass

delattr(x, y)

2.5、示例

1)示例一

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("万成置地","天露园")

#检测是否含有某属性
print(hasattr(b1,"name"))  #True
print(hasattr(b1,"sell_house"))  #以字符串的形式,否则NameError: name 'sell_house' is not defined
#print(hasattr(b1,rent_house))  #NameError: name 'sell_house' is not defined

#获取属性
print(getattr(b1,"name"))  #万成置地  ==>获取数据属性
func = getattr(b1,"rent_house")  #获取函数属性
func()   #万成置地 黑中介租房子啦

# getattr(b1,"abc")   #当属性不存在时报错AttributeError: 'BlackMedium' object has no attribute 'abc'
print(getattr(b1,"abc","不存在"))  #不存在  ==>当使用getattr获取属性时,当属性不存在,可以指定默认返回值

#设置属性
setattr(b1,"sb","True")  #设置数据属性
print(b1.__dict__)    #{'name': '万成置地', 'addr': '天露园', 'sb': 'True'}

setattr(b1,"show_name",lambda self:self.name+"_sb")  #设置函数属性
print(b1.__dict__)   #{'sb': 'True', 'name': '万成置地', 'show_name': <function <lambda> at 0x000001B354757F28>, 'addr': '天露园'}
print(b1.show_name(b1))  #万成置地_sb

#删除属性
print(b1.__dict__)  #{'name': '万成置地', 'sb': 'True', 'addr': '天露园', 'show_name': <function <lambda> at 0x0000029A9F8D7F28>}
delattr(b1,"addr")
delattr(b1,"show_name")
# delattr(b1,"show_name123")  #AttributeError: show_name123  属性不存在时报错
print(b1.__dict__)  #{'sb': 'True', 'name': '万成置地'}

2)示例二

class Foo(object):
    staticField = "oldboy"

    def __init__(self):
        self.name = name

    def func(self):
        return "func"

    @staticmethod
    def bar():
        return "bar"

print(getattr(Foo,"staticField"))  #oldboy
print(getattr(Foo,"func"))  #<function Foo.func at 0x00000237B782E378>
print(getattr(Foo,"bar"))   #<function Foo.bar at 0x00000228D871E400>

3)示例三

查看自身模块

import sys

def s1():
    print("s1")

def s2():
    print("s2")

this_module = sys.modules[__name__]
print(this_module)  #<module '__main__' from 'G:/python/反射.py'> ==> __main__表示当前模块

print(hasattr(this_module,"s1"))  #True
print(getattr(this_module,"s2"))  #<function s2 at 0x000001B74743E378>

4)示例四

导入其他模块,利用反射查看该模块是否存在某个方法

image

module_test.py:

def test():
    print('from the test')

index.py:

import aaa.module_test as obj

obj.test()   #from the test

print(obj)   #<module 'aaa.module_test' from 'G:\\python\\aaa\\module_test.py'>
print(hasattr(obj,'test'))  #True
print(getattr(obj,"test"))  #<function test at 0x0000024770C3E400>返回函数地址
getattr(obj,'test')()   #from the test

2.6、反射的好处

2.6.1、实现可插拔机制

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

class FtpClient:
    'ftp客户端,但是还么有实现具体的功能'
    def __init__(self,addr):
        print('正在连接服务器[%s]' %addr)
        self.addr=addr
    def put(self):
        print('正在上传文件')    #该方法还没有实现

#++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
from Ftpclient import FtpClient

f1=FtpClient('1.1.1.1')
# f1.put()

if hasattr(f1,'put'):
    func_get=getattr(f1,'put')
    func_get()
else:
    print('其他的逻辑')

2.6.2、动态导入模块(基于反射当前模块成员)

# from m1.t import *
from m1.t import test1,_test2  #可以这样导入
test1()  #test1
_test2() #test2
#当test2被封装了私有属性,不能以from m1.t import *被导入,NameError: name 'test2' is not defined

#当导入的模块时字符串时,如何导入呢?
module_t=__import__('m1.t')
print(module_t)  #<module 'm1' (namespace)>,打印的并不是m1.t,而是上一级m1
module_t.t.test1()  #test1

import  importlib   #导入importlib
m=importlib.import_module('m1.t')
print(m)   #<module 'm1.t' from 'G:\\python\\m1\\t.py'>
m.test1()  #test1
m._test2()  #test2

三、__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   #无线递归了,RecursionError: maximum recursion depth exceeded
        self.__dict__[key] = value

    def __delattr__(self, item):
        print("from __delattr")
        self.__dict__.pop(item)

f1 = Foo(10)   #from  __setattr,设置y=10,触发了__setattr__执行

#__setattr__添加或修改时会触发执行
print(f1.__dict__)   #{'y': 10}
f1.z = 3   #from  __setattr
print(f1.__dict__)   #{'y': 10, 'z': 3}

#__delattr__删除属性时会触发
f1.__dict__["a"] = 3  #可以直接修改属性字典,来完成添加/修改属性的操作
print(f1.__dict__)   #{'y': 10, 'a': 3, 'z': 3}
del f1.a             #from __delattr
print(f1.__dict__)   #{'y': 10, 'z': 3}

#__getattr__只有在使用点"."调用属性且属性不存在的时候才会触发
f1.abc   #from __getattr__
f.y      #存在时不触发

四、二次加工标准类型(包装)

4.1、包装

包装:python为大家提供了标准数据类型,以及丰富的内置方法,其实在很多场景下我们都需要基于标准数据类型来定制我们自己的数据类型,新增/改写方法,这就用到了继承/派生知识(其他的标准类型均可以通过下面的方式进行二次加工)

class List(list): #继承list所有的属性,也可以派生出自己新的,比如append和mid
    def append(self, p_object):
        ' 派生自己的append:加上类型检查'
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        super().append(p_object)

    @property
    def mid(self):
        '新增自己的属性'
        index=len(self)//2
        return self[index]

l=List([1,2,3,4])
print(l)  #[1, 2, 3, 4]
l.append(5)
print(l)  #[1, 2, 3, 4, 5]
# l.append('1111111') #报错,必须为int类型TypeError: must be int

print(l.mid)  #3

#其余的方法都继承list的
l.insert(0,-123)
print(l)   #[-123, 1, 2, 3, 4, 5]
l.clear()  
print(l)   #[]

示例二:

class List(list):
    def __init__(self,item,tag=False):
        super().__init__(item)
        self.tag=tag
    def append(self, p_object):
        if not isinstance(p_object,str):
            raise TypeError
        super().append(p_object)
    def clear(self):
        if not self.tag:
            raise PermissionError
        super().clear()

l=List([1,2,3],False)
print(l)  #[1, 2, 3]
print(l.tag)  #False

l.append('saf')
print(l)   #[1, 2, 3, 'saf']

# l.clear() #异常PermissionError

l.tag=True
l.clear()
print(l)  #[]

4.2、授权

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

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

示例一:

import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        self.file=open(filename,mode,encoding=encoding)
    def write(self,line):
        t=time.strftime('%Y-%m-%d %T')
        self.file.write('%s %s' %(t,line))

    def __getattr__(self, item):
        return getattr(self.file,item)

f1=FileHandle('b.txt','w+')
f1.write('你好啊')
f1.seek(0)
print(f1.read())
f1.close()

示例二:

import time
class FileHandle:
    def __init__(self,filename,mode='r',encoding='utf-8'):
        if 'b' in mode:
            self.file=open(filename,mode)  #"b"模式不需要指定encoding
        else:
            self.file=open(filename,mode,encoding=encoding)
        self.filename=filename
        self.mode=mode
        self.encoding=encoding

    def write(self,line):
        if 'b' in self.mode:
            if not isinstance(line,bytes):
                raise TypeError('must be bytes')
        self.file.write(line)  #self.file是文件句柄,封装了read,write等方法

    def __getattr__(self, item):
        return getattr(self.file,item)

    def __str__(self):
        if 'b' in self.mode:
            res="<_io.BufferedReader name='%s'>" %self.filename
        else:
            res="<_io.TextIOWrapper name='%s' mode='%s' encoding='%s'>" %(self.filename,self.mode,self.encoding)
        return res
f1=FileHandle('b.txt','wb')
# f1.write('你好啊') #自定制的write,不用在进行encode转成二进制去写了,简单,大气
f1.write('你好啊'.encode('utf-8'))
print(f1)   #<_io.BufferedReader name='b.txt'>
f1.close()

练习一:

class List:
    def __init__(self,seq):
        self.seq=seq

    def append(self, p_object):
        ' 派生自己的append加上类型检查,覆盖原有的append'
        if not isinstance(p_object,int):
            raise TypeError('must be int')
        self.seq.append(p_object)

    @property
    def mid(self):
        '新增自己的方法'
        index=len(self.seq)//2
        return self.seq[index]

    def __getattr__(self, item):
        return getattr(self.seq,item)

    def __str__(self):
        return str(self.seq)

l=List([1,2,3])
print(l)  #[1, 2, 3]
l.append(4)
print(l)  #[1, 2, 3, 4]
# l.append('3333333') #报错,必须为int类型
print(l.mid)  #3

#基于授权,获得insert方法
l.insert(0,-123)  #l中没有inser方法,会先在自己类中查找,没有会触发__getattr__==>self.seq=l, item=insert
print(l)  #[-123, 1, 2, 3, 4]

练习二:

class List:
    def __init__(self,seq,permission=False):
        self.seq=seq
        self.permission=permission
    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() #此时没有权限,抛出异常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]

五、__getattribute__

先来看__getattr__

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

    def __getattr__(self, item):
        print("执行的是__getattr__")
        # return self.__dict__[item]

f1 = Foo(10)
print(f1.x)   #10
f1.abc        #执行的是__getattr__,访问不存在的属性时,触发__getattr__

看__getattribute__:

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

    def __getattribute__(self, item):
        print("from __attribute__")

f1 = Foo(10)
f1.x   #from __attribute__,当属性存在时会执行
f1.xxxxx   #from __attribute__,当属性不存在时也会执行

将两者结合:

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

    def __getattr__(self, item):
        print("from __getattr__")

    def __getattribute__(self, item):
        print("from __attribute__")
        raise AttributeError("异常")

f1 = Foo(10)
f1.x   #from __attribute__
f1.xxxxx   #from __getattr_

#总结:当__getattribute__与__getattr__同时存在,只会执行__getattrbute__,除非__getattribute__在执行过程中抛出异常AttributeError

六、__setitem__,__getitem__,__delitem__

class Foo:
    def __getitem__(self, item):
        print('getitem',item)
        return self.__dict__[item]

    def __setitem__(self, key, value):
        print('setitem')
        self.__dict__[key]=value

    def __delitem__(self, key):
        print('delitem')
        self.__dict__.pop(key)

f1=Foo()
print(f1.__dict__)  #{}
# f1.name='egon'  #---->setattr-------->f1.__dict__['name']='egon'
f1['name']='egon'#--->setitem--------->f1.__dict__['name']='egon'
f1['age']=18

print('===>',f1.__dict__)  #===> {'age': 18, 'name': 'egon'}

# del f1.name
# print(f1.__dict__)
#
# print(f1.age)
del f1['name']
print(f1.__dict__)  #{'age': 18}

print(f1['age'])  #18

#总结:以"."的形式触发的是attr操作,以字典的影视触发的是item操作

七、自定义对象字符串显示

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

自定制格式化字符串__format__

7.1、__str__,__repr__

l=list('hello')
print(l)  #['h', 'e', 'l', 'l', 'o']
file=open('test.txt','w')
print(file)  #<_io.TextIOWrapper name='test.txt' mode='w' encoding='cp936'>

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    def __str__(self):
        return '名字是%s 年龄是%s' %(self.name,self.age)

f1=Foo('egon',18)
print(f1) #str(f1)--->f1.__str__()  #名字是egon 年龄是18
x=str(f1)
print(x)  #名字是egon 年龄是18
y=f1.__str__()
print(y)  #名字是egon 年龄是18
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

class Foo:
    def __init__(self,name,age):
        self.name=name
        self.age=age
    # def __str__(self):
    #     return '__str__'
    def __repr__(self):   #控制的是解释器的显示
        return '名字是%s 年龄是%s' %(self.name,self.age)

f1=Foo('egon',19)
# repr(f1)---->f1.__repr__()
print(f1) #str(f1)---》f1.__str__()---当str不存在时--->f1.__repr__()   #名字是egon 年龄是19
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

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

7.2、__format__

自定义格式化输出:

# class Date:
#     def __init__(self,year,mon,day):
#         self.year=year
#         self.mon=mon
#         self.day=day
# d1=Date(2016,12,26)
# x='{0.year}{0.mon}{0.day}'.format(d1)
# y='{0.year}:{0.mon}:{0.day}'.format(d1)
# z='{0.mon}-{0.day}-{0.year}'.format(d1)
# print(x)  #20161226
# print(y)  #2016:12:26
# print(z)  #12-26-2016

format_dic={
    'ymd':'{0.year}{0.mon}{0.day}',
    'm-d-y':'{0.mon}-{0.day}-{0.year}',
    'y:m:d':'{0.year}:{0.mon}:{0.day}'
}
class Date:
    def __init__(self,year,mon,day):
        self.year=year
        self.mon=mon
        self.day=day
    def __format__(self, format_spec):
        # print('我执行啦')
        # print('--->',format_spec)
        if not format_spec or format_spec not in format_dic:
            format_spec='ymd'
        fm=format_dic[format_spec]
        return fm.format(self)
d1=Date(2016,12,26)
# format(d1) #d1.__format__()
# print(format(d1))
print(format(d1,'ymd'))  #20161226
print(format(d1,'y:m:d'))  #2016:12:26
print(format(d1,'m-d-y'))  #12-26-2016
print(format(d1,'m-d:y'))  #20161226
print('===========>',format(d1,'asdd'))  #===========> 20161226

八、__slot__

8.1、__slot__介绍

1)__slots__是什么:是一个类变量,变量值可以是列表,元祖,或者可迭代对象,也可以是一个字符串(意味着所有实例只有一个数据属性)

2)引子:使用点来访问属性本质就是在访问类或者对象的__dict__属性字典(类的字典是共享的,而每个实例的是独立的)

3)为何使用__slots__:字典会占用大量内存,如果你有一个属性很少的类,但是有很多实例,为了节省内存可以使用__slots__取代实例的__dict__ ,你定义__slots__后,__slots__就会为实例使用一种更加紧凑的内部表示。实例通过一个很小的固定大小的数组来构建,而不是为每个实例定义一个 字典,这跟元组或列表很类似。在__slots__中列出的属性名在内部被映射到这个数组的指定小标上。使用__slots__一个不好的地方就是我们不能再给 实例添加新的属性了,只能使用在__slots__中定义的那些属性名

4)注意事项:__slots__的很多特性都依赖于普通的基于字典的实现。另外,定义了__slots__后的类不再 支持一些普通类特性了,比如多继承。大多数情况下,你应该 只在那些经常被使用到 的用作数据结构的类上定义__slots__,比如在程序中需要创建某个类的几百万个实例对象 。

5)关于__slots__的一个常见误区是它可以作为一个封装工具来防止用户给实例增加新的属性。尽管使用__slots__可以达到这样的目的,但是这个并不是它的初衷。更多的是用来作为一个内存优化工具。

8.2、示例

class Foo:
    __slots__=['name','age']  #{'name':None,'age':None}
    # __slots__='name' #{'name':None,'age':None}

f1=Foo()
f1.name='egon'
print(f1.name)  #egon

# f1.age=18  #--->__setattr__----->f1.__dict__['age']=18

# print(f1.__dict__)  #AttributeError: 'Foo' object has no attribute '__dict__' ==>实例没有__dict__属性
print(Foo.__slots__)  #['name', 'age']
print(f1.__slots__)   #['name', 'age']
f1.name='egon'
f1.age=17
print(f1.name)   #egon
print(f1.age)   #17
# f1.gender='male'  #AttributeError: 'Foo' object has no attribute 'gender'  ==>不能在添加新的属性

f2=Foo()
print(f2.__slots__)
f2.name='AAA'
f2.age=18
print(f2.name)  #AAA
print(f2.age)   #18

九、__doc__

class Foo:
    '我是描述信息'
    pass

print(Foo.__doc__)  #我是描述信息

#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
class Foo:
    '我是描述信息'
    pass

class Bar(Foo):
    pass
print(Bar.__doc__) #None 该属性无法继承给子类
print(Foo.__dict__)
#{'__doc__': '我是描述信息', '__module__': '__main__', '__weakref__': <attribute '__weakref__' of 'Foo' objects>, '__dict__': <attribute '__dict__' of 'Foo' objects>}
print(Bar.__dict__)  #{'__module__': '__main__', '__doc__': None}

十、__module__和__class__

__module__ 表示当前操作的对象在那个模块

__class__     表示当前操作的对象的类是什么

#!/usr/bin/env python
# -*- coding:utf-8 -*-

class C:

    def __init__(self):
        self.name = ‘SB'
#++++++++++++++++++++++++++++++++++++++++

from lib.aa import C

obj = C()
print obj.__module__  # 输出 lib.aa,即:输出模块
print obj.__class__      # 输出 lib.aa.C,即:输出类

十一、析构方法__del__

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

注:如果产生的对象仅仅只是python程序级别的(用户级),那么无需定义__del__,如果产生的对象的同时还会向操作系统发起系统调用,即一个对象有用户级与内核级两种资源,比如(打开一个文件,创建一个数据库链接),则必须在清除对象的同时回收系统资源,这就用到了__del__

class Foo:

    def __del__(self):
        print('执行我啦')

f1=Foo()
del f1   #删除实例,触发执行__del__
print('------->')

#输出结果
执行我啦
------->
#+++++++++++++++++++++++++++++++++++++++++

class Foo:

    def __del__(self):
        print('执行我啦')

f1=Foo()
# del f1
print('------->')

#输出结果
------->
执行我啦

典型的应用场景

创建数据库类,用该类实例化出数据库链接对象,对象本身是存放于用户空间内存中,而链接则是由操作系统管理的,存放于内核空间内存中,当程序结束时,python只会回收自己的内存空间,即用户态内存,而操作系统的资源则没有被回收,这就需要我们定制__del__,在对象被删除前向操作系统发起关闭数据库链接的系统调用,回收资源

这与文件处理是一个道理:

f=open('a.txt') #做了两件事,在用户空间拿到一个f变量,在操作系统内核空间打开一个文件
del f #只回收用户空间的f,操作系统的文件还处于打开状态

#所以我们应该在del f之前保证f.close()执行,即便是没有del,程序执行完毕也会自动del清理资源,于是文件操作的正确用法应该是
f=open('a.txt')
读写...
f.close()
很多情况下大家都容易忽略f.close,这就用到了with上下文管理

十二、__call__

class Foo:

    def __init__(self):
        pass
    
    def __call__(self, *args, **kwargs):

        print('__call__')


obj = Foo() # 执行 __init__
obj()       # 执行 __call__

十三、__next__,__iter__

class Foo:
    def __init__(self,n):
        self.n=n
    def __iter__(self):
        return self

    def __next__(self):
        if self.n == 13:
            raise StopIteration('终止了')
        self.n+=1
        return self.n

# l=list('hello')
# for i in l:
#     print(i)
f1=Foo(10)
# print(f1.__next__())  #11
# print(f1.__next__())  #12
# print(f1.__next__())  #13
# print(f1.__next__())  #StopIteration: 终止了

for i in f1:  # obj=iter(f1)------------>f1.__iter__()
     print(i)  #obj.__next_()

11
12
13

斐波那契数列:

class Fib:
    def __init__(self):
        self._a=1
        self._b=1

    def __iter__(self):
        return self
    def __next__(self):
        if self._a > 100:
            raise StopIteration('终止了')
        self._a,self._b=self._b,self._a + self._b  #交换值
        return self._a

f1=Fib()
print(next(f1))
print(next(f1))
print(next(f1))
print(next(f1))
print(next(f1))
print('==================================')
for i in f1:
    print(i)

#结果
1
2
3
5
8
==================================
13
21
34
55
89
144
posted @ 2019-09-08 10:30  运维人在路上  阅读(194)  评论(0编辑  收藏  举报