Notas principais do Python para desenvolvimento de teste (30): Aplicação do Python Reflection

Normalmente, quando manipulamos as propriedades ou métodos de um objeto, fazemos isso através do operador ponto ".". Por exemplo o seguinte código:

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)

A saída do código acima é:

Hello, my name is Chunming
Chunming

A reflexão é outro meio de manipular propriedades e métodos de objetos, como:

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

A saída do código acima é:

Hello, my name is Chunming
Chunming

Pode-se ver que é consistente com o resultado da passagem do operador ponto. getattr é uma função para obter um atributo ou método de objeto. A documentação oficial do Python descreve seu uso da seguinte forma:

getattr( objeto , nome [, padrão ])

Retorna o valor da propriedade nomeada do objeto. nome deve ser uma string. Se a string for uma das propriedades do objeto, o valor dessa propriedade será retornado. Por exemplo, getattr(x, 'foobar')é equivalente a x.foobar. Se a propriedade especificada não existir e um valor padrão for fornecido, ele será retornado, caso contrário, será acionado AttributeError.

Entenda o código acima de acordo com o documento, getattr(p, 'say_hi')pegue o valor do atributo say_hi do objeto p e atribua-o à variável func, pois o atributo say_hi é um método da classe Person, se você quiser chamar esse método, você precisa para executá -lo func(), você precisa getattr(p, "name")obter o atributo name do objeto p.

Além da função getattr para obter atributos e métodos de objetos, python também possui funções internas para julgar, definir e excluir atributos e métodos de objetos. Vamos dar uma olhada na descrição dessas três funções na documentação oficial do Python:

setattr( objeto , nome , valor )

Esta função corresponde a getattr()dois . Seus argumentos são um objeto, uma string e um valor arbitrário. String especificando uma propriedade existente ou uma nova propriedade. A função atribuirá um valor a esta propriedade, desde que o objeto permita esta operação. Por exemplo, setattr(x, 'foobar', 123)é equivalente a x.foobar = 123.

hasattr( objeto , nome )

Os argumentos são um objeto e uma string. Retorna se a string for o nome de uma das propriedades do objeto True, caso contrário retorna False. (Esta função é implementada chamando getattr(object, name)para ver se há AttributeErroruma exceção.)

delattr( objeto , nome )

setattr()funções relacionadas. Os argumentos são um objeto e uma string. A string deve ser alguma propriedade do objeto. Esta função excluirá a propriedade especificada se o objeto permitir. Por exemplo delattr(x, 'foobar'), é equivalente a del x.foobar.

O mecanismo para manipular atributos em Python através das quatro funções getattr, setattr, hasattr e delattr é a reflexão. É um mecanismo para manipular propriedades e métodos de objetos na forma de strings. Vamos aplicar as três funções setattr, hasattr e delattr ao atributo p para ver o efeito:

Determine se o objeto p tem o atributo say_bye e o atributo say_hi:

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

Adicione o método say_bye e o atributo age ao objeto p:

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

Ambas as propriedades estão agora acessíveis, via reflexão:

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

Ou acessado através do operador ponto:

p.say_bye()
print(p.age)

Remova o atributo idade:

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

Além das operações de reflexão de objetos, existem operações de reflexão de classes, operações de reflexão do módulo atual, operações de reflexão de outros módulos e operações de reflexão de outros pacotes.

  • reflexão de classe

As operações de reflexão de classe referem-se à execução de operações de reflexão em atributos de classe, métodos de classe ou métodos estáticos.

Obtenha as propriedades da classe:

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

Verifique se existe um atributo de classe:

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

Além disso, propriedades e métodos podem ser adicionados e removidos da classe.

print(delattr(Person, 'feed'))
print(hasattr(Person, 'feed'))
setattr(Person, 'feed', lambda x: print("Three times per day."))
print(hasattr(Person, 'feed'))
  • Operações refletidas para o módulo atual

O módulo atual também é o arquivo py onde está localizado o código atual.Reflexão também pode operar em variáveis ​​e funções no módulo atual. Por exemplo, um módulo contém uma função al para determinar se cada elemento no objeto de iteração é True. O conteúdo é o seguinte:

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__]Obtenha o nome do módulo atual através do método. O primeiro parâmetro de getattr é o nome do módulo e o segundo parâmetro é o atributo que você deseja obter do módulo.

  • Outras operações de reflexão do módulo

Execute operações de reflexão em funções, propriedades e variáveis ​​em outros módulos importados. Por exemplo, importamos o módulo heapq do Python, que fornece uma implementação do algoritmo de fila de heap, também conhecido como algoritmo de fila de prioridade. O código a seguir é uma função que implementa a classificação de heap.

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))])  # 弹出并从返回堆中最小的项

Aqui, não chamamos as funções no módulo heapq por heapq.heapifye . heapq.heappopEm vez disso, o mesmo efeito é alcançado através da reflexão.

Aprendeu o uso básico das quatro funções em reflexão. Então, qual é o ponto de reflexão? Qual é o seu cenário de aplicação? A resposta é que você pode usar a reflexão quando não tiver certeza se as propriedades e funções necessárias existem. Outro papel importante é melhorar a escalabilidade e a manutenção do código.

Suponha que mantenhamos todos os algoritmos de criptografia em um módulo chamado criptografia e permitamos que os usuários desse módulo adicionem mais algoritmos de criptografia a este módulo. O conteúdo do módulo de criptografia é o seguinte:

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

Entre eles, a função encrypt_something fornece um algoritmo de criptografia geral, e o chamador precisa passar o conteúdo a ser criptografado e o algoritmo de criptografia, portanto, quando o chamador usa o módulo encryption.py, ele só precisa importar a função encrypt_something. assim:

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

Ao analisar a função encrypt_something, descobrimos que quando adicionamos mais algoritmos de criptografia ao módulo encryption.py, precisamos modificar a função encrypt_something e adicionar uma nova ramificação if a ela. À medida que o algoritmo de criptografia aumenta, as ramificações da encrypt_something função se tornará cada vez mais muitos.

Depois de aprender a reflexão, a parte do código encrypt_something pode ser escrita assim:

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

Em comparação com a instrução if branch anterior, a reflexão é mais concisa e mais fácil de manter. Para adicionar novos métodos de criptografia, você só precisa adicionar mais algoritmos de criptografia ao módulo encryption.py. O código encrypt_something não requer nenhuma alteração.

Vejamos um exemplo de aplicação de reflexão em um script de teste baseado no framework de teste Pytest. Por exemplo, o conteúdo do arquivo conftest é o seguinte:

# 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 são todos os scripts de teste, esses são os arquivos py que _testterminam ou começam.test_

server = getattr(request.module, "smtpserver", "smtp.gmail.com") significa encontrar o atributo smtpserver do arquivo de script de teste, se não for encontrado, use smtp.gmail.com como o valor de smtpserver por padrão. Se o arquivo de script de teste tiver esse atributo, use o valor no script de teste, como o seguinte script de teste, smtpserver usará o valor de 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()

Comumente usado em muitas estruturas de código aberto, é uma arma para melhorar a capacidade de manutenção e a escalabilidade. Se o seu trabalho envolve o desenvolvimento de frameworks, a reflexão certamente ajudará. O acima é meu entendimento atual da reflexão do Python. Continuarei a atualizá-lo quando encontrar mais casos no futuro.

Acho que você gosta

Origin blog.csdn.net/liuchunming033/article/details/119712472
Recomendado
Clasificación