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 ax.foobar
. Se a propriedade especificada não existir e um valor padrão for fornecido, ele será retornado, caso contrário, será acionadoAttributeError
.
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 ax.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 retornaFalse
. (Esta função é implementada chamandogetattr(object, name)
para ver se háAttributeError
uma 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 exemplodelattr(x, 'foobar')
, é equivalente adel 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.heapify
e . heapq.heappop
Em 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 _test
terminam 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.