魔术方法 反射

魔术方法 反射

反射(reflection):指的是运行时获取类型定义信息。一个对象能够在运行时像照镜子一样反射出其类型信息;也就是说能够通过一个对象,找到自己的type、class、attribute、或method的能力,称为反射或者自省。 具有反射能力的函数:type、isinstance、callable、dir、getattr。 运行时和编译时不同,指的是程序倍加在到内存中执行的时候。

反射相关的函数和方法

內建函数 意义
getattr(object,name[,default]) 通过name返回object的属性值。当属性不存在将使用default返回,如果没有default,则返回异常。name必须使字符串
setattr(object,name,value) object的属性存在则覆盖,反之就增加
hasattr(object,name) 判断对象是否有这个名字的属性,name必须是字符串
class Point:
    def __init__(self,x,y):
        self.x = x
        self.y = y

    def __repr__(self):
        return 'Point({},{})'.format(self.x,self.y)

    def show(self):
        print(self)

p1 = Point(5,9)
p2 = Point(7,3)
print(repr(p1),repr(p2),sep ='\n')
print(p1.__dict__)
setattr(p1,'y',16)
setattr(p1,'z',10)
print(getattr(p1,'__dict__'))
#动态调用方法
if hasattr(p1,'show'):
    getattr(p1,'show')()
#动态增加方法,为类增加方法
if not hasattr(Point,'add'):
    setattr(Point,'add',lambda self,other: Point(self.x+other.x,self.y+other.y))

print(Point.add)
print(p1.add)
print(p1.add(p2))
#为实例增加方法,未绑定
if not hasattr(p1,'sub'):
    setattr(p1,'sub',lambda self,other: Point(self.x-other.x,self.y-other.y))
print(p1.sub(p1,p2))
print(p1.sub)
print(p1.__dict__)
print(Point.__dict__)

这种动态增加属性的方式使运行时改变类或者实例的方式,但是装饰器或Mixin都是定义时就决定了,一次反射能力具有更大的灵活性。 上面代码中通过dict访问对象的属性,本质上也是利用的反射的能力。

  • 练习 运用对象方式来实现分发器:
class Dispather:
    def __init__(self):
        self._run()

    def cmd1(self):
        print("I'm a command")

    def cmd2(self):
        print("I'm command2")
    def _run(self):
        while True:
            cmd = input('enter a command: ')
            if cmd.strip() == 'quit':
                break
            getattr(self,cmd,lambda :print('Unknown this command {}'.format(cmd)))()
Dispather()

反射相关的魔术方法

  1. __getatte__()
class Base:
    n = 0

class Point(Base):
    z = 6
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def show(self):
        print(self.x,self.y)
        
    def __getattr__(self, item):
        return "misssing {}".format(item)
p1 = Point(7,9)
print(p1.x)
print(p1.z,p1.n)
print(p1.t)

一个累的属性会按照集成关系查找,如果找不到就会执行魔法方法getattr,如果没有这个方法就会返回AttributeError异常,表示找不到属性 查找顺序:对象字典 ——> 类字典 ——> 类祖先的字典 ——>调用getattr

  1. __setattr__()
class Base:
    n = 0


class Point(Base):
    z = 6

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

    def show(self):
        print(self.x, self.y)

    def __getattr__(self, item):
        return "misssing {}".format(item)

    def __setattr__(self, key, value):
         print('setattr {} = {}'.format(key,value))


p1 = Point(7, 9)
print(p1.x) #missing,实例通过“.”设置属性,如同self.x = x,就会调用setattr,属性要加到实例的字典中就要自己完成
print(p1.z, p1.n)
print(p1.t)
p1.x =50
print(p1.__dict__)
p1.__dict__['x'] = 60
print(p1.x)
print(p1.__dict__)

魔法方法setattr可以拦截对实例属性的增加修改操作如果要设置生效,需要自己操作实例的字典。

  1. __delattr__()
class Point:
    Z = 99
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def __delattr__(self, item):
        print('can not del {}'.format(item))
p = Point(14,5)
del p.x
p.z =15
del p.z
del p.Z
print(Point.__dict__)
print(p.__dict__)
del Point.Z
print(Point.__dict__)

可以阻止通过实例删除属性的操作。但是通过类异常可以删除属性。

  1. __getattribute__
class Base:
    n=84
class Point(Base):
    z = 99
    def __init__(self,x,y):
        self.x = x
        self.y = y
    def __getattribute__(self, item):
        return "missint {}".format(item)

    def __delattr__(self, item):
        print('can not del {}'.format(item))
p = Point(14,5)
print(p.__dict__)
print(p.x)
print(p.z)
print(p.n)
print(p.t)
print(Point.__dict__)
print(Point.z)

魔法方法getattribute中为了避免无线递归,他的视线应该永远调用基类的同名方法以访问需要的任何属性,例如object.getattribute(self,name). 注意:除非明确知道getattribute用来做什么,否则不要使用

总结:

魔法方法 意义
__getattrbute__() 实例所有的属性调用都从这个方法开始
__getattr__() 当通过搜索实例、实例的类及祖先类查不到属性,就会调用此方法
__setattr__() 通过“.”访问实例属性,进行增加、修改都要调用它
__delattr__() 当通过实例来删除属性时调用此方法

属性查找顺序 实例调用getattribute ——> 实例字典 ——> 类的字典 ——> 祖先类的字典 ——> 调用getattr

描述器Desciptors

描述器的表现

用到三个魔术方法:__get__、__set__、__delete__ self指代当前实例;instance时owner的实例;owner是属性实例所属的类。

  1. object.__get__(self,instance,owner)
  2. object.__set__(self,instance,value)
  3. object.__delete__(self,instance)
class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')
class B:
    x =A()
    def __init__(self):
        print('b.init')
print('-'*20)
print(B.x.a1)
print('========================')
b = B()
print(b.x.a1)

根据运行结果来看,类加载的时候,类变量需要先生成,而类B的属性是类A的实例,所以先初始化类A,然后依次执行。

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print('A.get {} {} {}'.format(self,instance,owner))

class B:
    x =A()
    def __init__(self):
        print('b.init')

print('-'*20)
print(B.x)
#print(B.x.a1)
print('========================')
b = B()
print(b.x)
#print(b.x.a1)

因为定义了__get__方法,类A就变成了描述器,对类B或者类B的实例的x属性读取,称为对类A的实例的访问,就会调用__get__方法。__get__方法的三个参数分别是:self是类A的实例,owner是类B,instance是类B的实例。根据上面的结果得到给__get__一个返回值self就可以解决报错的问题。

描述器定义

Python中,一个类实现了__set__、__delete__、__get__三个方法1中的任意一个就是描述符。如果仅实现了__get__方法就是非数据描述器;同时实现了__get__、__set__就是数据描述符。如果一个类的类属性设置为描述器,那么它被称为owner属主。

属性的访问顺序

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print('A.get {} {} {}'.format(self,instance,owner))
        return self

    def __set__(self, instance, value):
        print('A.set {} {} {}'.format(self,instance,value))
        self.data = value

class B:
    x =A()
    def __init__(self):
        print('b.init')
        self.x = 'b.x'


print('-'*20)
print(B.x)
print(B.x.a1)
print('========================')
b = B()
print(b.x)
print(b.x.a1)

当在类B初始化时增加x属性后,抛出异常类型str没有a1属性,这是因为类B的势力中的x属性覆盖了类的属性x,我们在类A上增加set魔法方法后返回变成了a1。 总结出属性查找顺序:实例的字典优先于费数据描述器;数据描述器优先于实例的字典。 b.x = 500,这是调用数据描述器的set方法,或者调用非数据描述器的实例覆盖。 B.x = 500,赋值即定义,这是覆盖类属性

本质(进阶)

class A:
    def __init__(self):
        self.a1 = 'a1'
        print('A.init')

    def __get__(self, instance, owner):
        print('A.get {} {} {}'.format(self,instance,owner))
        return self

    # def __set__(self, instance, value):
    #     self.data = value

class B:
    x =A()
    def __init__(self):
        print('b.init')
        self.x = 'b.x'
        self.y = 'b.y'

b=B()
print(b.x)
print(b.y)
#print(b.x.a1)
print('字典')
print(b.__dict__)
print(B.__dict__)

根据上述代码中禁用set方法前后字典的不同结果发现,数据描述器只是没有把B初始化函数时的x属性添加到实例字典中,造成了该属性如果是数据描述器优先访问的假象。其实属性访问顺序从没变过。

Python中的描述器

Python中的静态方法和类方法都是非数据描述器的实现。因此实例可以重新定义和覆盖方法。这允许单个实例获取与同一类的其他势力不同的行为。 property()函数是一个数据描述器的实现。因此实例不能覆盖属性的行为。

  • 练习
  1. 实现staticmathod
class StaticMethod:
    def __init__(self,fn):
        self._fn = fn

    def __get__(self, instance, owner):
        return self._fn
class A:
    @StaticMethod
    def stmd():
        print('Static method')

A.stmd()
A().stmd()
  1. 实现classmethod
from functools import partial
class ClassMethod:
    def __init__(self,fn):
        self._fn = fn

    def __get__(self, instance, owner):
        print(self,instance,owner)
        return partial(self._fn,owner)
class A:
    @ClassMethod
    def stmd(cls):
        print('Static method')

A.stmd()
A().stmd()
  1. 对实例的数据进行校验,使用描述器实现
class Person:
    def __init__(self,name:str,age:int):
        params = ((name,str),(age,int))
        if not self.checkdata(params):
            raise TypeError
        self.name =name
        self.age =age
    def checkdata(self,params):
        for p,t in params:
            if not isinstance(p,t):
                return False
        return True
### 上述写法代码耦合太高,里边有硬编码,而且不标准,较为丑陋。使用描述器接和装饰器让代码和类表现的像内置类型一样。

import inspect

class CheckType:
    def __init__(self,name,type):
        self.name = name
        self.type = type

    def __get__(self, instance, owner):
        if instance is not None:
            return instance.__dict__[self.name]
        return self
	#此处判断传进的参数是否符合注解要求
    def __set__(self, instance, value):
        if not isinstance(value,self.type):
            raise TypeError
        instance.__dict__[self.name] = value

def typeassert(cls):
    params = inspect.signature(cls).parameters
    for name,param in params.items():
        if param.annotation != param.empty:
	        #相当于是cls.name = CheckType(name,param.annotation)
            setattr(cls,name,CheckType(name,param.annotation))
    return cls

@typeassert    #装饰器是为了给Person类加两个CheckType实例的类属性
class Person:
    def __init__(self,name:str,age:int):
        self.name = name
        self.age = age

p = Person('jerry',20)
print(p)
print(p.__dict__)
print(Person.__dict__)

本文来自投稿,不代表Linux运维部落立场,如若转载,请注明出处:http://www.178linux.com/89095

(0)
上一篇 2017-11-29 23:50
下一篇 2017-11-30 10:31

相关推荐

  • 计算机基础与Linux初识

    一、 计算机的组成及其功能。      计算机五大组成部件:运算器、控制器、存储器、输入设备、输出设备。其中CPU主要由运算器及控制器组成。      1、运算器:进行算数运算、逻辑运算,对数据进行加工处理。      2、控制器…

    2017-07-02
  • 如何新增加磁盘分区挂载

    新增磁盘和分区挂载我们先要了解fdisk的参数以及菜单的操作说明,下面是fdisk的参数说明:  fdisk参数说明: m :显示菜单和帮助信息 a :活动分区标记/引导分区 d :删除分区 l :显示分区类型 n :新建分区 p :显示分区信息 q :退出不保存 t :设置分区号 v :进行分区检查 w :保存修改 x :扩展应用,高级功能 1  查看磁盘…

    Linux干货 2018-03-26
  • Linux程序包管理方式

    Linux程序包安装和管理方式共计三种:          一、[yum|dnf],通过官网或者其他开源网站提供的文件服务器,本机镜像源等途径进行安装。         二、rpm,通过官网或者其他开源网站通过…

    Linux干货 2016-07-29
  • 马哥教育网络班22期+第1周课程练习

    第1周课程练习 一、描述计算机的组成及其功能。     CPU (运算器+控制器), 存诸器(内存与外部存储),输入输出设备。      运算器:对数据进行处理(包括算述运算与逻辑运算)。      控制器:负责从存储器取出指令,按指令的要求发出控制信号,使各…

    Linux干货 2016-08-15
  • Linux上实现rsyslog+mysql+loganalyz进行日志收集

    在我们的运维工作中,常常会对系统上的日志进行收集,手动管理少量的几台服务器的日志收集没有太大难度,但是企业当中批量的管理成千上万台服务器的时候,这时候想一台台的收集日志未免太浪费时间了,这时候我们需要一个批量管理日志的系统来解决这一难题,今天我给大家带来的使用 1、syslog介绍       &n…

    Linux干货 2016-10-24
  • 05linux用户和组的权限总结

    1、文件的权限分类 文件的权限对象分三类:属主(u)、属组(g)、其他(o),每个对象都有rwx,读写执行三类权限。 对于文件 r:可查看文件内容 w:可修改其类容 x:可把此文件提请内核启动为一个进程 对于目录 r:可使用ls查看此目录中文件列表 w:可在此目录中创建和删除文件 x:可使用ls查看目录中文件列表,可以cd进入此目录 X:只给目录x权限,不给…

    Linux干货 2016-11-27