Nota: Esta pergunta é apenas para fins informativos. Estou interessado em ver até que ponto é possível entrar no interior do Python.
Não faz muito tempo, iniciou-se uma discussão sobre uma certa questão sobre se as seqüências passadas para imprimir instruções poderiam ser modificadas após / durante a chamada print
. Por exemplo, considere a função:
def print_something():
print('This cat was scared.')
Agora, quando print
executado, a saída para o terminal deve exibir:
This dog was scared.
Observe que a palavra "gato" foi substituída pela palavra "cachorro". De alguma forma, algo em algum lugar foi capaz de modificar esses buffers internos para alterar o que foi impresso. Suponha que isso seja feito sem a permissão explícita do autor do código original (portanto, hackers / seqüestros).
Este comentário do sábio @abarnert, em particular, me fez pensar:
Existem algumas maneiras de fazer isso, mas todas são muito feias e nunca devem ser feitas. A maneira menos feia é provavelmente substituir o
code
objeto dentro da função por outro com umaco_consts
lista diferente . O próximo é provavelmente acessar a API C para acessar o buffer interno do str. [...]
Então, parece que isso é realmente possível.
Aqui está minha maneira ingênua de abordar esse problema:
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
Obviamente, exec
é ruim, mas isso realmente não responde à pergunta, porque na verdade não modifica nada durante quando / depois print
é chamado.
Como isso seria feito como o @abarnert explicou?
42
para do23
que por que é uma má idéia alterar o valor de"My name is Y"
para"My name is X"
.Respostas:
Primeiro, há realmente uma maneira muito menos invasiva. Tudo o que queremos fazer é mudar o que é
print
impresso, certo?Ou, da mesma forma, você pode monkeypatch em
sys.stdout
vez deprint
.Além disso, nada de errado com a
exec … getsource …
ideia. Bem, é claro que há muito errado com isso, mas menos do que se segue aqui…Mas se você deseja modificar as constantes de código do objeto de função, podemos fazer isso.
Se você realmente quiser brincar com objetos de código de verdade, use uma biblioteca como
bytecode
(quando terminar) oubyteplay
(até então, ou para versões mais antigas do Python) em vez de fazê-lo manualmente. Mesmo para algo tão trivial, oCodeType
inicializador é uma dor; se você realmente precisa fazer coisas como consertarlnotab
, apenas um lunático faria isso manualmente.Além disso, é evidente que nem todas as implementações do Python usam objetos de código no estilo CPython. Esse código funcionará no CPython 3.7 e, provavelmente, todas as versões voltarão para pelo menos 2.2 com algumas pequenas alterações (e não as coisas de hackers de código, mas coisas como expressões de gerador), mas não funcionará com nenhuma versão do IronPython.
O que poderia dar errado ao hackear objetos de código? Principalmente apenas segfaults,
RuntimeError
s que consomem toda a pilha,RuntimeError
s mais normais que podem ser manipulados ou valores de lixo que provavelmente apenas aumentam umTypeError
ouAttributeError
quando você tenta usá-los. Por exemplo, tente criar um objeto de código com apenas aRETURN_VALUE
com nada na pilha (bytecodeb'S\0'
para 3.6+ oub'S'
anterior) ou com uma tupla vazia paraco_consts
quando houver umLOAD_CONST 0
no bytecode ou comvarnames
decrementado por 1 para que o mais altoLOAD_FAST
realmente carregue um freevar / célula cellvar. Para se divertir de verdade, se vocêlnotab
errar o suficiente, seu código será apenas falhado quando executado no depurador.Usar
bytecode
oubyteplay
não protegê-lo de todos esses problemas, mas eles têm algumas verificações básicas de integridade e bons ajudantes que permitem que você insira um pedaço de código e se preocupe em atualizar todos os desvios e etiquetas para que você possa ' Não entendi errado, e assim por diante. (Além disso, eles impedem que você precise digitar o ridículo construtor de 6 linhas e depurar os erros de bobagem que surgem com isso.)Agora vamos para o # 2.
Mencionei que os objetos de código são imutáveis. E é claro que os consts são uma tupla, então não podemos mudar isso diretamente. E a coisa na tupla constante é uma string, que também não podemos mudar diretamente. É por isso que eu tive que criar uma nova string para criar uma nova tupla para criar um novo objeto de código.
Mas e se você pudesse alterar uma string diretamente?
Bem, profundo o suficiente debaixo das cobertas, tudo é apenas um ponteiro para alguns dados C, certo? Se você estiver usando o CPython, existe uma API C para acessar os objetos , e você pode usá-la
ctypes
para acessar a API a partir do próprio Python, o que é uma péssima idéia que eles colocampythonapi
ali noctypes
módulo do stdlib . :) O truque mais importante que você precisa saber é queid(x)
é o ponteiro real para ax
memória (como umint
).Infelizmente, a API C para seqüências de caracteres não nos permite acessar com segurança o armazenamento interno de uma sequência já congelada. Portanto, com segurança, vamos apenas ler os arquivos de cabeçalho e encontrar esse armazenamento.
Se você estiver usando o CPython 3.4 - 3.7 (é diferente para versões mais antigas e quem sabe o futuro), uma string literal de um módulo feito de ASCII puro será armazenada usando o formato ASCII compacto, que significa a estrutura termina cedo e o buffer de bytes ASCII segue imediatamente na memória. Isso quebrará (como provavelmente no segfault) se você colocar um caractere não ASCII na string ou certos tipos de strings não literais, mas você poderá ler as outras 4 maneiras de acessar o buffer para diferentes tipos de strings.
Para tornar as coisas um pouco mais fáceis, estou usando o
superhackyinternals
projeto no meu GitHub. (Não é intencionalmente instalável pelo pip, porque você realmente não deveria usá-lo, exceto para experimentar a compilação local do intérprete e assim por diante.)Se você quiser brincar com essas coisas,
int
é muito mais simples do que issostr
. E é muito mais fácil adivinhar o que você pode quebrar alterando o valor de2
para1
, certo? Na verdade, esqueça de imaginar, vamos fazê-lo (usando os tipos desuperhackyinternals
novo):… Finja que a caixa de código tem uma barra de rolagem de comprimento infinito.
Tentei a mesma coisa no IPython e, na primeira vez em que tentei avaliar
2
no prompt, ele entrou em algum tipo de loop infinito ininterrupto. Presumivelmente, ele está usando o número2
para algo em seu loop REPL, enquanto o intérprete de ações não está?fonte
PyUnicodeObject
, de outro lado, isso é provavelmente realmente só Python no sentido de que um interpretador Python vai executá-lo ...NameError: name 'arg' is not defined
. Você quis dizerargs = [arg.replace('cat', 'dog') if isinstance(arg, str) else arg for arg in args]
:? Uma maneira indiscutivelmente melhor para escrever este seria:args = [str(arg).replace('cat', 'dog') for arg in args]
. Outro, ainda mais curto, a opção:args = map(lambda a: str(a).replace('cat', 'dog'), args)
. Isso tem o benefício adicional deargs
ser preguiçoso (o que também pode ser conseguido substituindo a compreensão da lista acima por uma geradora -*args
funciona de qualquer maneira).PyUnicodeObject
definição de struct, mas copiar isso para a resposta seria um obstáculo e acho que o leia-me e / ou os comentários da fonte parasuperhackyinternals
realmente explicar como acessar o buffer (pelo menos bem o suficiente para me lembrar da próxima vez que me importo; não tenho certeza se será suficiente para mais alguém ...), que eu não queria entrar aqui. A parte relevante é como passar de um objeto Python ao vivo para suaPyObject *
viactypes
. (E talvez simulando a aritmética de ponteiro, evitando automáticaschar_p
conversões, etc.)print
a um nome. Você também pode vincular o nomeprint
para eles:import yourmodule; yourmodule.print = badprint
.Patch de macaco
print
print
é uma função interna, portanto, ela usará aprint
função definida nobuiltins
módulo (ou__builtin__
no Python 2). Portanto, sempre que você desejar modificar ou alterar o comportamento de uma função interna, basta reatribuir o nome nesse módulo.Este processo é chamado
monkey-patching
.Depois disso, todas as
print
chamadas serão realizadascustom_print
, mesmo que elasprint
estejam em um módulo externo.No entanto, você realmente não deseja imprimir texto adicional, deseja alterar o texto impresso. Uma maneira de fazer isso é substituí-lo na string que seria impressa:
E, de fato, se você executar:
Ou se você escrever isso em um arquivo:
test_file.py
e importe-o:
Então, ele realmente funciona como planejado.
No entanto, caso você queira temporariamente apenas imprimir um patch de macaco, você pode agrupá-lo em um gerente de contexto:
Portanto, quando você executa, isso depende do contexto impresso:
Então é assim que você pode "hackear"
print
usando patches de macaco.Modifique o destino em vez do
print
Se você olhar para a assinatura,
print
notará umfile
argumento que ésys.stdout
por padrão. Observe que esse é um argumento padrão dinâmico (ele realmente aparecesys.stdout
toda vez que você chamaprint
) e não é um argumento padrão normal do Python. Portanto, se você alterarsys.stdout
print
, realmente imprimirá para o destino diferente, ainda mais conveniente que o Python também forneça umaredirect_stdout
função (do Python 3.4 em diante, mas é fácil criar uma função equivalente para as versões anteriores do Python).A desvantagem é que não funcionará para
print
declarações que não são impressassys.stdout
e que a criação de suas própriasstdout
não é realmente simples.No entanto, isso também funciona:
Resumo
Alguns desses pontos já foram mencionados pelo @abarnet, mas eu queria explorar essas opções com mais detalhes. Especialmente como modificá-lo entre módulos (usando
builtins
/__builtin__
) e como fazer essa alteração apenas temporariamente (usando gerenciadores de contexto).fonte
redirect_stdout
, então é bom ter uma resposta clara que leve a isso.Uma maneira simples de capturar toda a saída de uma
print
função e processá-la é alterar o fluxo de saída para outra coisa, por exemplo, um arquivo.Vou usar um
PHP
convenções de nomenclatura ( ob_start , ob_get_contents , ...)Uso:
Imprimiria
fonte
Vamos combinar isso com a introspecção de quadros!
Você verá que esse truque antecede todas as saudações com a função ou método de chamada. Isso pode ser muito útil para registrar ou depurar; especialmente porque permite "seqüestrar" as instruções de impressão no código de terceiros.
fonte