파이썬의 인쇄 기능을 "해킹"할 수 있습니까?
참고: 이 질문은 정보 제공을 위한 것입니다.저는 파이썬의 내부를 얼마나 깊이 파고들 수 있는지 알고 싶습니다.
얼마 전에, 특정 질문 안에서 다음 통화 후/통화 중에 문을 인쇄하기 위해 전달된 문자열을 수정할 수 있는지에 대한 논의가 시작되었습니다.print
만들어 졌습니다.예를 들어, 다음과 같은 기능을 고려합니다.
def print_something():
print('This cat was scared.')
자, 제.print
실행하면 이 다음과 같이됩니다.
This dog was scared.
"cat"라는 단어가 "dog"라는 단어로 대체되었습니다.어딘가에서 인쇄된 내용을 바꾸기 위해 내부 버퍼를 수정할 수 있었습니다.원본 코드 작성자의 명시적인 허가 없이 수행되었다고 가정합니다(따라서 해킹/히재킹).
특히 현명한 @abarnert의 이 논평은 저에게 다음과 같은 생각을 하게 했습니다.
그렇게 하는 데는 몇 가지 방법이 있지만, 모두 매우 추악하고 절대 해서는 안 됩니다.가장 추악하지 않은 방법은 아마도 그것을 교체하는 것입니다.
code
에 있는 가 다른 개체입니다.co_consts
list.C버퍼에 입니다. C API에 접근하는 것입니다.
그래서, 이것은 실제로 가능해 보입니다.
이 문제에 접근하는 저의 순진한 방법은 다음과 같습니다.
>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.
물이야론.exec
나쁘지만, 그것은 실제로 언제/이후에 아무것도 수정하지 않기 때문에 질문에 대답하지 않습니다. print
이 호출됩니다.
@abarnert가 설명한 것처럼 그것은 어떻게 이루어질까요?
첫째, 훨씬 덜 촌스러운 방법이 있습니다.우리가 원하는 것은 무엇인가를 바꾸는 것입니다.print
지문, 맞지요?
_print = print
def print(*args, **kw):
args = (arg.replace('cat', 'dog') if isinstance(arg, str) else arg
for arg in args)
_print(*args, **kw)
또는 비슷하게, 당신은 원숭이 패치를 할 수 있습니다.sys.stdout
에 print
.
또한, 아무 문제가 없습니다.exec … getsource …
물론 잘못된 점은 많지만 여기에 따르는 것보다는 덜합니다.
그러나 함수 개체의 코드 상수를 수정하려면 그렇게 할 수 있습니다.
실제로 코드 객체를 가지고 놀고 싶다면 수동으로 하는 대신 (완료 시) 또는 (그때까지 또는 이전 Python 버전의 경우)와 같은 라이브러리를 사용해야 합니다.이렇게 사소한 것에도 불구하고,CodeType
이니셜라이저는 고통입니다; 만약 당신이 실제로 수리와 같은 것들을 해야 한다면.lnotab
미치광이만이 수동으로 그렇게 할 것입니다.
또한 모든 Python 구현체가 Cython 스타일 코드 객체를 사용하는 것은 아님은 말할 필요도 없습니다.이 코드는 CPython 3.7에서 작동할 것이며, 아마도 모든 버전이 최소한 2.2 이상으로 거슬러 올라가며 약간의 작은 변경 사항(코드 해킹 내용이 아니라 제너레이터 식과 같은 것)이 있지만, IronPython의 어떤 버전과도 작동하지 않을 것입니다.
import types
def print_function():
print ("This cat was scared.")
def main():
# A function object is a wrapper around a code object, with
# a bit of extra stuff like default values and closure cells.
# See inspect module docs for more details.
co = print_function.__code__
# A code object is a wrapper around a string of bytecode, with a
# whole bunch of extra stuff, including a list of constants used
# by that bytecode. Again see inspect module docs. Anyway, inside
# the bytecode for string (which you can read by typing
# dis.dis(string) in your REPL), there's going to be an
# instruction like LOAD_CONST 1 to load the string literal onto
# the stack to pass to the print function, and that works by just
# reading co.co_consts[1]. So, that's what we want to change.
consts = tuple(c.replace("cat", "dog") if isinstance(c, str) else c
for c in co.co_consts)
# Unfortunately, code objects are immutable, so we have to create
# a new one, copying over everything except for co_consts, which
# we'll replace. And the initializer has a zillion parameters.
# Try help(types.CodeType) at the REPL to see the whole list.
co = types.CodeType(
co.co_argcount, co.co_kwonlyargcount, co.co_nlocals,
co.co_stacksize, co.co_flags, co.co_code,
consts, co.co_names, co.co_varnames, co.co_filename,
co.co_name, co.co_firstlineno, co.co_lnotab,
co.co_freevars, co.co_cellvars)
print_function.__code__ = co
print_function()
main()
코드 객체를 해킹하면 무엇이 잘못될 수 있습니까?대부분은 단순한 결함일 뿐입니다.RuntimeError
을 다 치우는 은 더 정상적인 것입니다.RuntimeError
수 s 입니다.TypeError
또는AttributeError
당신이 그것들을 사용하려고 할 때.예를 들어, 다음과 같은 코드 개체를 만들어 보십시오.RETURN_VALUE
스택에바도없상는태코드것아트이무바(▁with)코)b'S\0'
3.6 이상의 경우b'S'
빈 이전다대에빈한튜플음또는튜▁before플▁an빈▁withle.co_consts
가 있을 때LOAD_CONST 0
바이트 코드에서 또는 를 사용하여varnames
1만큼 감소했기 때문에 가장 높음LOAD_FAST
실제로 freevar/cellvar 셀을 로드합니다.정말 재미로, 만약 당신이 그것을 얻는다면.lnotab
충분히 틀렸습니다. 코드는 디버거에서 실행될 때만 segfault입니다.
용사를 합니다.bytecode
또는byteplay
이러한 모든 문제로부터 여러분을 보호하지는 못하겠지만, 기본적인 건전성 검사와 코드 조각을 삽입하고 모든 오프셋과 레이블을 업데이트하는 등의 작업을 할 수 있는 훌륭한 도우미가 있습니다. (게다가, 그들은 여러분이 그 말도 안 되는 6줄 생성자를 입력할 필요가 없도록 해줍니다.그리고 그렇게 함으로써 발생하는 바보 같은 오타를 디버깅해야 합니다.)
자, 이제 2번.
나는 코드 객체는 불변이라고 언급했습니다.그리고 물론 콘스트는 튜플이기 때문에 우리는 그것을 직접적으로 바꿀 수 없습니다.그리고 구성 요소 안에 있는 것은 끈인데, 우리는 그것을 직접 바꿀 수도 없습니다.그것이 제가 새로운 코드 객체를 만들기 위해 새로운 튜플을 만들기 위해 새로운 문자열을 만들어야 했던 이유입니다.
하지만 만약 당신이 직접 문자열을 바꿀 수 있다면 어떨까요?
음, 충분히 깊은 곳에서, 모든 것은 단지 몇몇 C 데이터에 대한 포인터일 뿐입니다, 그렇죠?CPython을 사용하는 경우 객체에 액세스하기 위한 C API가 있으며, Python 자체 내에서 이 API에 액세스하는 데 사용할 수 있습니다. 이는 너무 끔찍한 아이디어여서 그들이 stdlib의 모듈에 바로 들어갑니다. :) 당신이 알아야 할 가장 중요한 트릭은id(x)
는 에대실포니다입터인제한▁to▁pointer▁actual다▁the▁is에 대한 실제 포인터입니다.x
int
).
안타깝게도 문자열에 대한 C API를 사용하면 이미 고정된 문자열의 내부 저장소에 안전하게 도달할 수 없습니다.그러니 안전하게, 그냥 헤더 파일을 읽고 스토리지를 직접 찾아보자구요.
CPython 3.4 - 3.7을 사용하는 경우(이전 버전에서는 다르며, 미래에는 누가 알고 있음) 순수 ASCII로 만들어진 모듈의 문자열 리터럴이 콤팩트 ASCII 형식을 사용하여 저장됩니다. 이는 구조가 일찍 종료되고 ASCII 바이트의 버퍼가 메모리에 즉시 따라간다는 것을 의미합니다.ASC가 아닌 것을 넣으면 (아마도 segfault에서와 같이) 깨집니다.문자열의 II 문자 또는 특정 종류의 비문학적 문자열이지만 다른 종류의 문자열에 대한 버퍼에 액세스하는 다른 4가지 방법에 대해 읽어볼 수 있습니다.
좀 더 쉽게 하기 위해, 저는 GitHub에서 프로젝트를 사용하고 있습니다.(통역기의 로컬 빌드 등을 실험하는 것 외에는 사용하면 안 되기 때문에 의도적으로 pip 설치가 불가능합니다.)
import ctypes
import internals # https://github.com/abarnert/superhackyinternals/blob/master/internals.py
def print_function():
print ("This cat was scared.")
def main():
for c in print_function.__code__.co_consts:
if isinstance(c, str):
idx = c.find('cat')
if idx != -1:
# Too much to explain here; just guess and learn to
# love the segfaults...
p = internals.PyUnicodeObject.from_address(id(c))
assert p.compact and p.ascii
addr = id(c) + internals.PyUnicodeObject.utf8_length.offset
buf = (ctypes.c_int8 * 3).from_address(addr + idx)
buf[:3] = b'dog'
print_function()
main()
것을 놀고 , 물가놀싶다면고지고을건이,▁with▁if면,int
훨씬 더 단순합니다.str
그리고 가치를 변화시킴으로써 무엇을 깨뜨릴 수 있는지를 훨씬 더 쉽게 추측할 수 있습니다.2
1
,그렇죠?사실, 상상하는 것은 잊어버리고, 그냥 하자 (에서 나온 유형 사용).superhackyinternals
다시):
>>> n = 2
>>> pn = PyLongObject.from_address(id(n))
>>> pn.ob_digit[0]
2
>>> pn.ob_digit[0] = 1
>>> 2
1
>>> n * 3
3
>>> i = 10
>>> while i < 40:
... i *= 2
... print(i)
10
10
10
코드 상자에 무한 길이 스크롤 막대가 있다고 가정합니다.
아이피톤에서도 똑같은 시도를 했고, 처음으로 평가하려고 했을 때2
프롬프트에서, 그것은 일종의 연속적인 무한 루프에 들어갔습니다.아마도 그것은 그 번호를 사용하고 있을 것입니다.2
주식 통역사는 그렇지 않은 반면, REP 루프에 있는 무언가를 위해?
print
print
합니다.print
「 」에 builtins
모또(으)로 표시됩니다.__builtin__
Python 2)에서 확인할 수 있습니다.따라서 기본 제공 함수의 동작을 수정하거나 변경할 때마다 해당 모듈의 이름을 재할당하면 됩니다.
이 프로세스를 다음과 같이 부릅니다.monkey-patching
.
# Store the real print function in another variable otherwise
# it will be inaccessible after being modified.
_print = print
# Actual implementation of the new print
def custom_print(*args, **options):
_print('custom print called')
_print(*args, **options)
# Change the print function globally
import builtins
builtins.print = custom_print
후에는 매번print
통화가 통합니다.custom_print
설령 그렇다 하더라도print
는 외부 모듈에 있습니다.
그러나 추가 텍스트를 인쇄하지 않고 인쇄된 텍스트를 변경하려고 합니다.한 가지 방법은 인쇄되는 문자열로 교체하는 것입니다.
_print = print
def custom_print(*args, **options):
# Get the desired seperator or the default whitspace
sep = options.pop('sep', ' ')
# Create the final string
printed_string = sep.join(args)
# Modify the final string
printed_string = printed_string.replace('cat', 'dog')
# Call the default print function
_print(printed_string, **options)
import builtins
builtins.print = custom_print
실제로 실행하면 다음과 같습니다.
>>> def print_something():
... print('This cat was scared.')
>>> print_something()
This dog was scared.
또는 파일에 기록하는 경우:
test_file.py
def print_something():
print('This cat was scared.')
print_something()
가져오기:
>>> import test_file
This dog was scared.
>>> test_file.print_something()
This dog was scared.
그래서 그것은 정말 의도한 대로 작동합니다.
그러나 일시적으로 원숭이 패치 인쇄만 원하는 경우 컨텍스트 관리자에서 이를 래핑할 수 있습니다.
import builtins
class ChangePrint(object):
def __init__(self):
self.old_print = print
def __enter__(self):
def custom_print(*args, **options):
# Get the desired seperator or the default whitspace
sep = options.pop('sep', ' ')
# Create the final string
printed_string = sep.join(args)
# Modify the final string
printed_string = printed_string.replace('cat', 'dog')
# Call the default print function
self.old_print(printed_string, **options)
builtins.print = custom_print
def __exit__(self, *args, **kwargs):
builtins.print = self.old_print
따라서 이를 실행할 때는 출력되는 컨텍스트에 따라 달라집니다.
>>> with ChangePrint() as x:
... test_file.print_something()
...
This dog was scared.
>>> test_file.print_something()
This cat was scared.
그래서 당신이 "해킹"할 수 있는 방법입니다.print
몽둥이로
대상을 수정합니다.print
서명을 보면 다음과 같은 것을 알 수 있습니다.file
sys.stdout
결석으로이것은 동적 기본 인수입니다(정말 조회됩니다).sys.stdout
전화할 때마다print
Python의 일반적인 기본 인수와는 다릅니다.그래서 당신이 바뀌면,sys.stdout
print
실제로는 Python이 기능도 제공하기 때문에 훨씬 더 편리하게 다른 대상으로 인쇄할 수 있습니다(Python 3.4 이후부터). 그러나 이전 Python 버전에 대해 동등한 기능을 만드는 것은 쉽습니다.
단점은 그것이 작동하지 않는다는 것입니다.print
인지않문는으로 되지 않는sys.stdout
만의 리고당것창을것는조하의만신그▁your것▁creating▁own▁and▁that는하조창.stdout
정말 간단하지 않습니다.
import io
import sys
class CustomStdout(object):
def __init__(self, *args, **kwargs):
self.current_stdout = sys.stdout
def write(self, string):
self.current_stdout.write(string.replace('cat', 'dog'))
그러나 이것도 작동합니다.
>>> import contextlib
>>> with contextlib.redirect_stdout(CustomStdout()):
... test_file.print_something()
...
This dog was scared.
>>> test_file.print_something()
This cat was scared.
요약
이러한 점 중 일부는 @abarnet에서 이미 언급했지만, 저는 이러한 옵션에 대해 더 자세히 알아보고 싶었습니다. 모듈 ()builtins
/__builtin__
(콘텍스트 관리자를 사용하여) 일시적으로만 변경하는 방법을 설명합니다.
든출캡간처방단법한는에서 모든 하는 간단한 print
출력 스트림을 다른 것(예: 파일)으로 변경하는 것입니다.
사용합니다.PHP
이름 지정 규칙(ob_start, ob_get_messages, ...)
from functools import partial
output_buffer = None
print_orig = print
def ob_start(fname="print.txt"):
global print
global output_buffer
print = partial(print_orig, file=output_buffer)
output_buffer = open(fname, 'w')
def ob_end():
global output_buffer
close(output_buffer)
print = print_orig
def ob_get_contents(fname="print.txt"):
return open(fname, 'r').read()
용도:
print ("Hi John")
ob_start()
print ("Hi John")
ob_end()
print (ob_get_contents().replace("Hi", "Bye"))
인쇄할 것
안녕 존 바이 존
이것을 프레임 검사와 결합해 보겠습니다!
import sys
_print = print
def print(*args, **kw):
frame = sys._getframe(1)
_print(frame.f_code.co_name)
_print(*args, **kw)
def greetly(name, greeting = "Hi")
print(f"{greeting}, {name}!")
class Greeter:
def __init__(self, greeting = "Hi"):
self.greeting = greeting
def greet(self, name):
print(f"{self.greeting}, {name}!")
이 트릭은 모든 인사말 앞에 호출 기능 또는 메서드가 있는 것을 발견할 수 있습니다.이것은 특히 서드 파티 코드로 문을 "히잭"할 수 있기 때문에 로깅 또는 디버깅에 매우 유용할 수 있습니다.
언급URL : https://stackoverflow.com/questions/49271750/is-it-possible-to-hack-pythons-print-function
'bestsource' 카테고리의 다른 글
Excel: 셀 색상을 기준으로 조건식을 만들 수 있습니까? (0) | 2023.05.04 |
---|---|
Git가 지정된 파일을 무시하지 않는 이유는 무엇입니까? (0) | 2023.05.04 |
SQL Server 날짜/시간을 더 짧은 날짜 형식으로 변환 (0) | 2023.05.04 |
ssh를 통해 git 클론 생성 (0) | 2023.05.04 |
데이터 행 수가 1,048,576개 이상인 Excel CSV. 파일 (0) | 2023.05.04 |