博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

第十二节 Python基础之面向对象(进阶)

Posted on 2017-02-26 13:42  Jasonhy  阅读(126)  评论(0)    收藏  举报

    在上一节中,我们讲到讲完了面向对象的一些基本属性和它的三大特性,这节我们将来学习一些更高级的东西.

   反射:广义上的反射指的是程序可以访问,检测和修改它本身状态或行为的一种能力,而python里面的反射就是通过字符串的形式来操作对象的相关属性,涉及到四个反射的方法是:hasattr,getattr,setattr和delattr,我们直接在案例中来看这四个方法的使用:

class People:
    country = "中国人"
    def __init__(self,name,language):
        self.name = name
        self.language = language
    def speek_lang(self):
        print("【%s】 是一名 【%s】 他的母语是 【%s】"%(self.name,self.country,self.language))
p = People("小王","汉语")
# hasattr(object,name):判断某一个对象中是否有某属性,比如
print(hasattr(p,"country"))
# getattr(object,name,defualt=None):获取对象中的某一个属性,如果没有该属性,在第三个参数不给的情况下就直接报错,如果我们给上第三个参数的,就会按照第三个参数返回
print(getattr(p,"country"))     # 输出结果是: 中国人
# print(getattr(p,"age"))     # 报错: AttributeError: 'People' object has no attribute 'age'
print(getattr(p,"age","找不到这个属性"))       # 输出的结果是:找不到这个属性
# setattr(object,name,value): 设置属性 如果属性不存在就会添加,如果属性存在,就修改
setattr(p,"age",18)
setattr(p,"name","老王")
print(p.__dict__)       # 输出结果是:{'name': '老王', 'language': '汉语', 'age': 18}

# delattr(object,name): 删除属性
delattr(p,"age")
print(p.__dict__)       # 输出结果是: {'name': '老王', 'language': '汉语'}

 

   那么反射的作用什么呢?一般情况下,我们都是团队开发,每个人负责的模块可能不一样,但是有的时候我可能用到别人模块下的某一个方法,这个时候我们不确定有没有没有这个方法,这个时候我们就可以通过反射来完成了,比如:

# A 开发的模块userinfo
class
UserInfo: def __init__(self,name,age): self.name = name self.age = age def get_name(self): ''' 在这里,我们假设用登录之后,我们就将用户名保存起来,然后将用户名返回 :return: ''' return self.name
# B 开发模块
from
userinfo import UserInfo class ShopCard: def __init__(self,user,goods): self.user = user self.goods = goods def enter_shoping_card(self): ''' 我们在进入购物车之前,要先判断用户是否登录,这个时候我们需要去用户信息哪里获取用户信息 但是我们又不确定那个用户信息有没有记录下来,这个时候我们就要用到反射了 :return: ''' if hasattr(self.user,"get_name"): print("欢迎【%s】 购物车有商品数为【%s】个" % (getattr(self.user,"get_name")(), self.goods)) else: print("你还没登录") user = UserInfo("小王",18) sc = ShopCard(user,"10000") sc.enter_shoping_card()

 

还有一个作用就是,我们可以动态导入模块,比如__import__("模块"),导入的是最顶层的那个,importlib.import_module("模块")导入的最里面的那个,那么有的时候,我们想自己来控制增删改查呢?这时候机会涉及到底层的是三个函数了,分别是:__setattr__,__getattr__,__delattr__,如:

typed={
    "name":str,
    "age":int,
    "language":str
}
class People:
    country = "中国人"
    def __init__(self,name,age,language):
        self.name = name
        self.age = age
        self.language = language
    def __setattr__(self, key, value):
        '''
        这个方法我们在进行实例化和改变值的时候都会调用
        :param key:
        :param value:
        :return:
        '''
        print("触发了set方法")
        # 由于我们重写了__setattr__方法,所以我们需要在这里重新将值设置到字典里面去
        # 前面我们说,我们希望在实例化的时候对参数进行一定的限制,比如,我们希望输入的年龄是整型,如果不是整型我们就给用于一个提示,让用户重新输入
        t = typed[key]
        if not isinstance(value,t):
            print("您输入的【%s】不是【%s】" % (key, typed[key]))
            return
        self.__dict__[key] = value
    def __getattr__(self, item):
        '''
        这个方法是当我们在获取属性的时候如果这个属性不存在就会触发
        :param item:
        :return:
        '''
        print("触发了get方法")

    def __delattr__(self, item):
        '''
        删除属性的时候就会调用
        :param item:
        :return:
        '''
        print("触发了del方法")


p = People("小王","18","汉语")
print(p.__dict__)

注:__getattribute__属性存在或不存在都会执行,当它与__getattr__同时存在的时候,如果__getattribute__的执行没有抛出异常的话,就只执行__getattribute__,如果通过raise AttributeError 抛出异常的话才会执行__getattr__

    item序列,适用字典操作,分别为:__getitem__,__setitem__,__delitem__,通过self.__dict__[key] = value来进行设值

   如果我们要改变字符串的显示对象,我们可以通过重写__str__,__repr__,__format__来实现,如:

format_dict={
    "format_type_one":"{obj.name}-{obj.age}-{obj.language}",
    "format_type_two":"{obj.name}:{obj.age}:{obj.language}",
    "format_type_three":"{obj.name}/{obj.age}/{obj.language}"
}
class People:
    country = "中国人"
    def __init__(self,name,age,language):
        self.name = name
        self.age = age
        self.language = language

    def __str__(self):
        '''
        我们在print的时候会调用此属性
        :return:
        '''
        return "%s,%s,%s"%(self.name,self.age,self.language)

    def __repr__(self):
        '''
        如果没有没有定义__str__就会调用这个方法
        :return:
        '''
        return "%s,%s,%s"%(self.name,self.age,self.language)

    def __format__(self, format_spec):
        '''
        当用format函数的时候就会执行,在这里,我们可以修改显示格式
        :param format_spec: 传入的参数
        :return:
        '''
        if not format_spec or format_spec not in format_dict:
            format_spec = "format_type_one"
        fmt = format_dict[format_spec]
        return fmt.format(obj = self)
p = People("小王",18,"普通话")
print(p)
print(format(p,"format_type_one"))

 

   slots属性: __slots__是一个类变量,变量值可以是列表,元组,或者可迭代对象,也可以是一个字符串;引子--在使用点来访问属性本质就是在访问类或者对象的__dict__属性字典;那么我们为什么使用__slots__呢?因为字典会占用大量内存,假设有一个属性很少的类,但是却有很多实例,为了节省内存这个时候我们就可以使用__slots__来取代__dict__,当我们定义了__slots__之后,__slots_就会实例一种更加紧凑的内部表示,实例通过一个很小的固定数组来构建,而不是为每个实例定义字典,和列表或数组很像,在__slots__中列出的属性名在内部被映射到这个数组指定的脚标上,使用__slots__一个不好的地方就是我们不能再给实例添加新的属性了,只能使用__slots__中定义的属性了,需要注意的是,__slots__的很多特性都依赖于普通的基于字典的实现,定义了__slots__的类不再支持一些普通类的特性,比如多继承,同时也没有__dict__这个属性了

class People:
    __slots__=["name","age"]

p = People()
p.name = "小王"
p.age = 18
print(p.__slots__)
print(p.name)

   __doc__:文档信息,这个函数是不能被继承的,比如:

class A:
    '''省略1000000行,自己去想象'''
    pass

class B(A):
    pass

print(A.__doc__)        # 输出结果是:省略1000000行,自己去想象
print(B.__doc__)        # 输出结果是:None

 

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

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

   __del__:析构方法,当对象在内存中被释放时,自动触发执行,一般我们是不需要去定义的,python解释器会自动进行垃圾回收的

   __call__:对象实例化的时候就会触发

   迭代器协议:

# 实现斐波那契数列
# 1、1、2、3、5、8、13、21、……
class Series:
    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

s = Series()
for i in s:
    if i > 20:
        break
    print(i)

   描述符: 本身就是一个新式类,在这个新式类中,至少实现__get__,__set__,__delete__中的的一个,也被称为描述符协议,它的作用就是代理另外一个类的属性(必须把描述符定义成这个类的类属性,不能定义到构造函数中),描述符本身调用时不执行的,定义在另外的类中,然后通过这个类的实例去执行相应的方法的时候才会被调用

    __get__:调用一个属性的时候触发

    __set__:为一个属性赋值的时候触发

    __delete__:采用del删除属性的时候触发

   描述符分为两种,一种是数据描述符,就是至少实现了__get__和__set__,另一种是非数据描述符,没有实现__set__方法,数据描述符的优先级大于非数据描述符,既然说到优先级,那么我们就看描述符的优先级:

    类属性 > 数据描述符 > 实例属性 > 非数据描述符 > 找不到属性时触发

class Descriptor:
    def __set__(self, instance, value):
        print("set")

    def __get__(self, instance, owner):
        print("get")

    def __delete__(self, instance):
        print("delete")


class Student:
    '''
    通过描述符来代理name属性
    '''
    name = Descriptor()

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

s = Student("小王",18)
# print(s.__dict__)       # 输出结果是:{'age': 18} ==>通过描述符的代理之后,name就变成了一个类属性,不再是实例属性了,既然是一个类属性,那么我们就可以直接通过类名来调用
#
# print(Student.name)     # 执行了get方法,但是我们发现输出是None,我们通过赋值看看情况
#
# Student.name = "老王"
# print(Student.name)
# print(s.name)       # 两个的输出结果我们发现 实例的name被覆盖了,而且描述符的get方法也没有执行,这就说明了 类属性优先于描述符

s.name = "小华"       # set方法调用了
print(s.name)         # get 方法被调用了

print(s.__dict__)       # 输出结果是:{'age': 18} 从而说明了数据数据大于实例属性 因为name没有在实例字典中
class Descriptor:
    '''
    非数据描述符
    '''
    def __get__(self, instance, owner):
        print("get")

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

    def __getattr__(self, item):
        print("找不到%s"%item)

s = Student("小王",18)
print(s.name)
print(s.__dict__)       # 输出结果是:{'name': '小王', 'age': 18} 同时也没有调用描述符中的get方法 表明了实例属性优先级大于非数据描述符

 

   描述符案例一:

# 通过描述符限制类型
class Descriptor:
    def __init__(self,type_name,excepted_type):
        '''

        :param type_name: 传入的数据
        :param excepted_type:   希望的数据类型
        '''
        self.type_name = type_name
        self.excepted_type = excepted_type
    def __set__(self, instance, value):
        if not isinstance(value,self.excepted_type):
            raise TypeError("异常是%s"%str(self.excepted_type))
        instance.__dict__[self.type_name] = value
    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.type_name]


class Employee:
    name = Descriptor("name",str)
    salary = Descriptor("salary",float)
    def __init__(self,name,salary):
        '''
        我们期望的是通过描述符限制我们的数据类型 比如name为str 而salary则为 float类型
        :param name:
        :param salary:
        '''
        self.name = name
        self.salary = salary
    def __str__(self):
        return "%s,%s"%(self.name,self.salary)
e = Employee("小王",2222)
print(e)

 

   描述符案例二:

# 通过上面的案例,我们发现,当我们需要限制很多属性的时候,我们就要定义很多个Descriptor实例,这样一来我们的代码就会看的很臃肿,接下来我们直接通过描述器来对类的一种装饰
class Descriptor:
    def __init__(self,key,excepted_type):
        self.key = key
        self.excepted_type = excepted_type

    def __set__(self, instance, value):
        if not isinstance(value,self.excepted_type):
            raise TypeError("异常是%s"%str(self.excepted_type))
        instance.__dict__[self.key] = value

    def __get__(self, instance, owner):
        if instance is None:
            return self
        return instance.__dict__[self.key]

def typed(**kwargs):
    '''
    定义一个装饰器
    :param kwargs: 传进来的数据 我们以字典的形式
    :return:
    '''
    def wrapper(cls):
        '''
        参数是一个类命名 我们在调用setatttr的时候需要三个参数
        第一个参数是:实例
        第二个参数:key
        第三个参数是:value
        :param cls:
        :return:
        '''
        for key,excepted_type in kwargs.items():
            '''
            在设置值的时候,我们先通过装饰器来做一个判断
            '''
            setattr(cls,key,Descriptor(key,excepted_type))
        return cls
    return wrapper
@typed(name=str,salary=float)
class Employee:
    def __init__(self,name,salary):
        self.name = name
        self.salary = salary

    def __str__(self):
        return "%s,%s"%(self.name,self.salary)

e = Employee("小王",2222.2)
print(e)
e = Employee("老王",22222)
print(e)

   描述符案例三:

# 自定制一个property,来实现缓存计算功能
class LazyProperty:
    '''
    我们定义一个非数据型描述符,因为实例属性优先级大于它,我们在第一次执行的
    就将它存到字典里面 等到下次用的时候就直接从字典里面获取
    '''
    def __init__(self,func):
        self.func = func

    def __get__(self, instance, owner):
        if instance is None:
            return self
        # 这里相当于执行了函数func
        value = self.func(instance)
        # 将value保存到自己的属性字典中
        setattr(instance,self.func.__name__,value)
        return value

class Room:
    def __init__(self,width,length):
        self.width = width
        self.length = length

    @LazyProperty   # 相当于area = LazyProperty(area)
    def area(self):
        return self.width * self.length

r = Room(10,10)
print(r.area)       # 先从自己的属性字典中,没有的话再去类中找,然后触发area的__get__方法 这样就不用每执行一次都要去计算一次
print(r.area)

 

   上下文管理协议: with ... as ...它的用处就是把代码块放在with中执行,在with执行结束之后,自动清理工作,无序手动干预;有的时候,我们需要管理一些资源,比如文件,网络连接和锁编程的环境中,可以在__exit__中定制自动释放资源的机制

    __enter__:with开始触发时,如果with里面有异常,将直接跳到exit()进行操作,如果这个时候__exit__返回的是True,这个时候with代码块里面的异常部分之后的代码将不会执行,直接跳出with,执行其他代码,如果没有返回True,整个程序这个时候就会报出异常,程序执行结束

    __exit__:正常情况下,with里面的代码执行完之后,触发

   元类:简单的说就是类的类,是类的模板,是用来控制如何创建类的,元类的实例是类,type是python内建的一个元类,用来控制生成类,通过type来生成类的话,里面有三个参数,第一个参数类名,第二个参数是父类,第三个参数是属性(必须是一个字典),比如:

A = type("A",(object,),{'x':1})
print(A.__dict__)

 

    如果我们要自定义一个元类的话,我们可以:

      一:声明为元类

        class A(metaclass=type):

      二:继承来实现:

        class A(type):