Python反射机制实例讲解

目录
  • 1. 反射的四个函数
  • 2. 类的反射操作
  • 3. 当前模块的反射操作
  • 4. 其他模块反射操作
  • 5. 反射应用场景之一
  • 6. 反射应用场景之二
  • 7. 总结

通常,我们操作对象的属性或者方法时,是通过点“.”操作符进行的。例如下面的代码:

class Person:
    type = "mammal"

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

    def say_hi(self):
        print('Hello, my name is', self.name)

    @staticmethod
    def feed():
        print("Three times per day.")

    @classmethod
    def sleep(cls):
        print("8 hours!")

p = Person('Chunming')
p.say_hi()
print(p.name)

上面代码的输出是

Hello, my name is Nikhil
Nikhil

反射是另外一种操作对象属性和方法的手段,例如:

func = getattr(p, 'say_hi')
func()
print(getattr(p, "name"))

上面这段代码的输出是:

Hello, my name is Nikhil
Nikhil

可见与通过点操作符的结果一致。

1. 反射的四个函数

getattr是获取对象属性或方法的函数,Python的官方文档是这样描述其用法的:

getattr(object, name, value)

返回对象命名属性的值。name必须是字符串。如果该字符串是对象的属性之一,则返回该属性的值。例如, getattr(x, ‘foobar')等同于 x.foobar。如果指定的属性不存在,且提供了 default值,则返回它,否则触发 AttributeError。

根据文档理解上述代码,getattr(p, ‘say_hi') 获取了p对象的say_hi属性值并赋值给func变量,因为say_hi属性在Person类中是一个方法,要想调用这个方法, 需要执行func(),getattr(p, “name”) 则是获取p对象的name属性。

除了获取对象属性和方法的getattr函数,python还内置了判断、设置、删除对象属性和方法的函数,来看看Python官方文档对这三个函数的说明:

setattr(object, name, value)

此函数与 getattr() 两相对应。其参数为一个对象、一个字符串和一个任意值。字符串指定一个现有属性或者新增属性。函数会将值赋给该属性,只要对象允许这种操作。例如,setattr(x, ‘foobar', 123) 等价于 x.foobar = 123。

hasattr(object, name)

该实参是一个对象和一个字符串。如果字符串是对象的属性之一的名称,则返回 True,否则返回 False。(此功能是通过调用 getattr(object, name) 看是否有 AttributeError 异常来实现的。)

delattr(object, name)

setattr() 相关的函数。实参是一个对象和一个字符串。该字符串必须是对象的某个属性。如果对象允许,该函数将删除指定的属性。例如 delattr(x, ‘foobar') 等价于 del x.foobar 。

Python中通过getattr、setattr、hasattr和delattr四个函数操作属性的机制就是反射。是通过字符串的形式操作对象属性和方法的机制。下面对p属性应用setattr、hasattr和delattr这三个函数看看效果:

判断p对象是否有say_bye属性和say_hi属性:

print(hasattr(p, 'say_bye'))  # 输出False
print(hasattr(p, 'say_hi'))  # 输出True

给p对象增加say_bye的方法和age属性:

setattr(p, 'say_bye', say_bye)
setattr(p, 'age', 18)

现在可以访问这两个属性了,通过反射访问:

bye = getattr(p, 'say_bye')
bye()
print(getattr(p, 'age'))

或者通过点操作符访问:

p.say_bye()
print(p.age)

删除age属性:

delattr(p, 'age')
print(hasattr(p, 'age'))  # 输出False

2. 类的反射操作

除了对象的反射操作,还有类的反射操作,当前模块的反射操作,还有其他模块的反射操作,其他包的反射操作。

类的反射操作,指的是对类属性、类方法或者静态方法执行反射操作。

获取类属性:

t = getattr(Person, 'type')
print(t)  # 输出mammal
f = getattr(Person, 'feed')
f()  # 输出Three times per day.
s = getattr(Person, 'sleep')
s() # 8 hours!

判断类属性是否存在:

print(hasattr(Person, 'type'))  # 输出True
print(hasattr(Person, 'name'))  # 输出False
print(hasattr(Person, 'say_hi')) # 输出True
print(hasattr(Person, 'sleep'))  # 输出True
print(hasattr(Person, 'feed'))  # 输出True

此外,还可以对类添加和删除属性和方法。

print(delattr(Person, 'feed'))
print(hasattr(Person, 'feed'))
setattr(Person, 'feed', lambda x: print("Three times per day."))
print(hasattr(Person, 'feed'))

3. 当前模块的反射操作

当前模块也就是当前代码所在的py文件,反射也可以对当前模块中的变量和函数进行操作。例如某个模块包含一个al函数,用来判断迭代对象中每个元素是否都是True,内容如下:

import sys

def al(iterable):
    for element in iterable:
        if not element:
            return False
    return True

this_module = sys.modules[__name__]

if hasattr(this_module, 'al'):
    all_true = getattr(this_module, 'al')
    result = all_true([1, 2, 3, 4, True, 0])
    print(result)

通过sys.modules[name]方法获取当前模块的名称。getattr第一个参数是模块名称,第二个参数是想要从模块中获取的属性。

4. 其他模块反射操作

对import进来的其他模块中的函数、属性、变量进行反射操作。例如,我们导入Python的heapq模块,这块模块提供了堆队列算法的实现,也称为优先队列算法。下面的代码是一个实现堆排序的函数。

import heapq

h = [(5, 'write code'), (7, 'release product'), (1, 'write spec'), (3, 'create tests')]

if hasattr(heapq, 'heapify'):
   heapi = getattr(heapq, 'heapify')  # 获取heapify属性
   heapi(h)  # 建堆
   if hasattr(heapq, 'heappop'):
       heapp = getattr(heapq, 'heappop')  # 获取heappop属性
       print([heapp(h) for _ in range(len(h))])  # 弹出并从返回堆中最小的项

这里,我们并没有通过heapq.heapify和heapq.heappop方式调用heapq模块中的函数。而是通过反射达到的同样的效果。

5. 反射应用场景之一

了解了反射中四个函数的基本用法。那么反射到底有什么用呢?它的应用场景是什么呢?答案是,当不确定所需要的属性和函数是否存在时,可以使用反射。另外一个重要作用是,可以提高代码的扩展性和可维护性。

假如我们把所有的加密算法都放到一个叫做encryption的模块中维护 ,并且允许使用这个模块的用户添加更多的加密算法到这个模块中。encryption的模块内容如下:

import hashlib
import os
import sys

def md5(content=None):
    """生成字符串的SHA256值"""
    if content is None:
        return ''
    md5_gen = hashlib.md5()
    md5_gen.update(content.encode('utf-8'))
    md5code = md5_gen.hexdigest()
    return md5code

def sha256(content=None):
    """生成字符串的SHA256值"""
    if content is None:
        return ''
    sha256_gen = hashlib.sha256()
    sha256_gen.update(content.encode('utf-8'))
    sha256code = sha256_gen.hexdigest()
    return sha256code

def sha256_file(filename):
    """生成文件的SHA256值"""
    if not os.path.isfile(filename):
        return ""
    sha256gen = hashlib.sha256()
    size = os.path.getsize(filename)  # 获取文件大小,单位是Byte
    with open(filename, 'rb') as fd:  # 以二进制方式读取文件
        while size >= 1024 * 1024:  # 当文件大于1MB时分块读取文件内容
            sha256gen.update(fd.read(1024 * 1024))
            size -= 1024 * 1024
        sha256gen.update(fd.read())
    sha256code = sha256gen.hexdigest()
    return sha256code

def md5_file(filename):
    """生成文件的MD5值"""
    if not os.path.isfile(filename):
        return ""
    md5gen = hashlib.md5()
    size = os.path.getsize(filename)  # 获取文件大小,单位是Byte
    with open(filename, 'rb') as fd:
        while size >= 1024 * 1024:  # 当文件大于1MB时分块读取文件内容
            md5gen.update(fd.read(1024 * 1024))
            size -= 1024 * 1024
        md5gen.update(fd.read())
    md5code = md5gen.hexdigest()
    return md5code

def encrypt_something(something, algorithm):
    """
    通用加密算法
    :param something: 待加密的内容,字符串或者文件
    :param algorithm: 加密算法
    :return:  加密后的内容
    """
    result = ""
    if algorithm == "md5":
        result = md5(something)
    elif algorithm == "sh256":
        result = sha256(something)
    elif algorithm == "sh256_file":
        result = sha256_file(something)
    elif algorithm == "md5_file":
        result = md5_file(something)
    return result

其中,encrypt_something函数提供了通用加密算法,需要调用者传入待加密的内容和加密算法,这样当调用者使用encryption.py模块时,只需导入encrypt_something函数即可。就像这样:

import encryption
print(encryption.encrypt_something("learn_python_by_coding", "sh256"))
print(encryption.encrypt_something("learn_python_by_coding", "md5"))

通过分析encrypt_something函数发现,当我们在encryption.py模块添加更多的加密算法后,就要修改encrypt_something函数,在其中增加新的if分支,随着加密算法的增加,encrypt_something函数的分支会越来越多。

学了反射之后,encrypt_something代码部分就可以这样写:

def encrypt_something(something, algorithm):
    """
    通用加密算法
    :param something: 待加密的内容,字符串或者文件
    :param algorithm: 加密算法
    :return:  加密后的内容
    """
    this_module = sys.modules[__name__]
    if hasattr(this_module, algorithm):
        algorithm = getattr(this_module, algorithm)
        result = algorithm(something)
    else:
        raise ValueError("Not support {} algorithm".format(algorithm))
    return result

相比前面的采用if分支语句方式,反射更加简洁明了,可维护性更强,要想增加新的加密方法,只需要在encryption.py模块添加更多的加密算法即可,encrypt_something代码不需要任何变更。

6. 反射应用场景之二

再看一个基于Pytest测试框架的测试脚本中应用反射的例子,比如conftest文件内容如下:

# content of conftest.py
import pytest
import smtplib

@pytest.fixture(scope="module")
def smtp_connection(request):
    server = getattr(request.module, "smtpserver", "smtp.gmail.com")
    smtp_connection = smtplib.SMTP(server, 587, timeout=5)
    yield smtp_connection
    print("finalizing {} ({})".format(smtp_connection, server))
    smtp_connection.close()

request.module 是所有测试脚本,就是那些以_test结尾或者test_开头的py文件。

server = getattr(request.module, "smtpserver", "smtp.gmail.com")

含义就是从测试脚本文件中找smtpserver属性,如果找不到,默认使用smtp.gmail.com作为smtpserver的值。如果测试脚本文件有这个属性,则使用测试脚本中的值,例如下面这个测试脚本,smtpserver则会使用mail.python.org这个值:

# content of test_anothersmtp.py

smtpserver = "mail.python.org"  # will be read by smtp fixture

def test_showhelo(smtp_connection):
    assert 0, smtp_connection.helo()

7. 总结

在很多开源框架中普遍采用,是提高可维护性和扩展性的利器。如果工作中也涉及到框架开发,反射一定会助一臂之力,,希望大家以后多多支持我们!

(0)

相关推荐

  • 简单谈谈python的反射机制

    对编程语言比较熟悉的朋友,应该知道"反射"这个机制.Python作为一门动态语言,当然不会缺少这一重要功能.然而,在网络上却很少见到有详细或者深刻的剖析论文.下面结合一个web路由的实例来阐述python的反射机制的使用场景和核心本质. 一.前言 def f1(): print("f1是这个函数的名字!") s = "f1" print("%s是个字符串" % s) 在上面的代码中,我们必须区分两个概念,f1和"f1

  • python通过实例讲解反射机制

    一.反射机制简介: 通过字符串的形式导入模块 通过字符串的形式,去模块中寻找指定的函数,并执行 规定用户输入格式 模块名/函数名 通过__import__的形式导入模块,并通过 hasattr和getattr 检查并获取函数返回值. 相关方法: getattr:--根据字符串的形式去某个模块中寻找东西 hasattr:--根据字符串的形式去某个模块中判断东西是否存在 setattr:--根据字符串的形式去某个模块中设置东西 delattr:--根据字符串的形式去某个模块中删除东西 二.反射机制初

  • Python类反射机制使用实例解析

    这篇文章主要介绍了Python类反射机制使用实例解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 反射就是通过字符串的形式,导入模块:通过字符串的形式,去模块寻找指定函数并执行. Python有四个内置函数: 函数 功能 getattr(object, attr[, default]) 获取指定字符串名称的对象属性或方法,如果对象有该属性则返回属性值,如果有该方法则返回该方法的内存地址,如果都没有就报错,如果指定了默认值找不到不会报错会取默认

  • Python动态导入模块和反射机制详解

    一.前言 何谓动态导入模块,就是说模块的导入可以根据我们的需求动态的去导入,不是像一般的在代码文件开头固定的导入所需的模块. 何谓反射机制,利用字符串的形式在模块或对象中操作(查找/获取/删除/添加)成员. 下面进入具体实例介绍环节.先创建一个示例文件example.py,简单写入几个加减乘除函数,如下,方便下文讲解使用. flag = 1 # 此变量在介绍反射机制时会用到 def my_sum(a, b): return a + b def my_sub(a, b): return a - b

  • 小结Python的反射机制

    前言: 前两天用Python实现了ftp服务器.在小项目中就用到了反射.因此写个笔记巩固下. 反射的定义:检测和修改它本身状态或行为的一种能力(自省). 而通过反射,Python可以通过字符串的映射或修改程序运行的状态和方法. 反射的四个方法.hasattr,getattr,setattr,delattr hasattr:判断一个方法是否存在与这个类中 class Person(object): def __init__(self,name): self.name = name def talk

  • 简单了解python反射机制的一些知识

    反射 反射机制就是在运行时,动态的确定对象的类型,并可以通过字符串调用对象属性.方法.导入模块,是一种基于字符串的事件驱动. 解释型语言:程序不需要编译,程序在运行时才翻译成机器语言,每执行一次都要翻译一次.因此效率比较低.相对于编译型语言存在的,源代码不是直接翻译成机器语言,而是先翻译成中间代码,再由解释器对中间代码进行解释运行.比如Python/JavaScript / Perl /Shell等都是解释型语言. python是一门解释型语言,因此对于反射机制支持很好.在python中支持反射

  • Python反射机制实例讲解

    目录 1. 反射的四个函数 2. 类的反射操作 3. 当前模块的反射操作 4. 其他模块反射操作 5. 反射应用场景之一 6. 反射应用场景之二 7. 总结 通常,我们操作对象的属性或者方法时,是通过点"."操作符进行的.例如下面的代码: class Person: type = "mammal" def __init__(self, name): self.name = name def say_hi(self): print('Hello, my name is

  • Python反射用法实例简析

    本文实例讲述了Python反射用法.分享给大家供大家参考,具体如下: class Person: def __init__(self): self.name = "zjgtan" def getName(self): return self.name 反射的简单含义: 通过类名获得类的实例对象 通过方法名得到方法,实现调用 反射方法一: from person import Person theObj = globals()["Person"]() print th

  • PHP反射机制案例讲解

    简介 就算是类成员定义为private也可以在外部访问,不用创建类的实例也可以访问类的成员和方法. PHP自5.0版本以后添加了反射机制,它提供了一套强大的反射API,允许你在PHP运行环境中,访问和使用类.方法.属性.参数和注释等,其功能十分强大,经常用于高扩展的PHP框架,自动加载插件,自动生成文档,甚至可以用来扩展PHP语言.由于它是PHP內建的oop扩展,为语言本身自带的特性,所以不需要额外添加扩展或者配置就可以使用.更多内容见官方文档. 反射类型 PHP反射API会基于类,方法,属性,

  • GoLang反射机制深入讲解

    目录 反射 反射类型Type 指针 结构体 反射值Value 结构体 空与有效性判断 修改值 函数调用 反射三定律 interface 底层结构 iface eface 反射 Go语言提供了reflect 包来访问程序的反射信息:定义了两个重要的类型Type和Value: reflect.TypeOf:获取任意值的类型对象(reflect.Type): reflect.ValueOf:获得值的反射值对象(reflect.Value): 反射类型Type Go语言程序中的类型(Type)指的是系统

  • Java 反射机制实例详解

    Java 反射机制实例详解 一.JAVA是动态语言吗? 一般而言,说到动态言,都是指在程序运行时允许改变程序结构或者变量类型,从这个观点看,Java和C++一样,都不是动态语言. 但JAVA它却有着一个非常突出的动态相关机制:反射.通过反射,Java可以于运行时加载.探知和使用编译期间完全求和的类.生成其对象实体,调用其方法或者对属性设值.所以Java算是一个半动态的语言吧. 反射的概念: 在Java中的反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法; 对于任意一个对

  • js 事件的传播机制(实例讲解)

    事件的默认传播机制: 捕获阶段:从外向里依次查找元素 目标阶段:从当前事件源本身的操作 冒泡阶段:从内到外依次触发相关的行为(我们最常用的就是冒泡阶段) 具体见下图: <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Document</title> <style> #outer{ margin

  • Java反射机制实例代码分享

    本文旨在对Java反射机制有一个全面的介绍,希望通过本文,大家会对Java反射的相关内容有一个全面的了解. 阅读本文之前,大家可先行参阅<重新理解Java泛型>. 前言 Java反射机制是一个非常强大的功能,在很多大型项目比如Spring, Mybatis都可以看见反射的身影.通过反射机制我们可以在运行期间获取对象的类型信息,利用这一特性我们可以实现工厂模式和代理模式等设计模式,同时也可以解决Java泛型擦除等令人苦恼的问题.本文我们就从实际应用的角度出发,来应用一下Java的反射机制. 反射

  • python中文分词,使用结巴分词对python进行分词(实例讲解)

    在采集美女站时,需要对关键词进行分词,最终采用的是python的结巴分词方法. 中文分词是中文文本处理的一个基础性工作,结巴分词利用进行中文分词. 其基本实现原理有三点: 1.基于Trie树结构实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的有向无环图(DAG) 2.采用了动态规划查找最大概率路径, 找出基于词频的最大切分组合 3.对于未登录词,采用了基于汉字成词能力的HMM模型,使用了Viterbi算法 安装(Linux环境) 下载工具包,解压后进入目录下,运行:python set

  • python开根号实例讲解

    平方根,又叫二次方根,表示为[√ ̄],如:数学语言为:√ ̄16=4.语言描述为:根号下16=4. 以下实例为通过用户输入一个数字,并计算这个数字的平方根: 例如 num = float(input('请输入一个数字: ')) num_sqrt = num ** 0.5 print(' %0.3f 的平方根为 %0.3f'%(num ,num_sqrt)) 以上代码输出结果为 请输入一个数字: 4 4.000 的平方根为 2.000 在该实例中,我们通过用户输入一个数字,并使用指数运算符 ** 来

  • python自动化发送邮件实例讲解

    在python中,通过如下两个模块可以实现邮件的自动化操作 smtplib email smtplib模块是对SMTP协议的封装,用于发送邮件:email模块用于构建邮件内容,支持以下3种形式的邮件 纯文本 html 带附件 首先来看下邮件的构建,对于一封邮件,需要指定发件人,收件人,主题,正文等内容,以最简单的纯文本邮件为例,构建方式如下 >>> from email.mime.text import MIMEText >>> from email.header im

随机推荐