python - क्या पायथन के प्रिंट फ़ंक्शन को "हैक" करना संभव है?




python-3.x printing (3)

बंदर-पैच print

print एक बिलिन फंक्शन है इसलिए यह __builtin__ मॉड्यूल में परिभाषित print फंक्शन (या __builtin__ 2 में __builtin__ ) का उपयोग करेगा। इसलिए जब भी आप एक अंतर्निहित फ़ंक्शन के व्यवहार को संशोधित या बदलना चाहते हैं, तो आप बस उस मॉड्यूल में नाम को पुन: असाइन कर सकते हैं।

इस प्रक्रिया को 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 से 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 कॉल करते हैं) और पायथन में सामान्य डिफ़ॉल्ट तर्क की तरह नहीं। इसलिए यदि आप sys.stdout print बदलते हैं, तो वास्तव में अलग-अलग लक्ष्य पर print होगा और भी अधिक सुविधाजनक है कि पायथन एक redirect_stdout फ़ंक्शन (पायथन 3.4 पर से) प्रदान करता है, लेकिन पहले पायथन संस्करणों के लिए एक समान फ़ंक्शन बनाना आसान है।

नकारात्मक पक्ष यह है कि यह print स्टेटमेंट के लिए काम नहीं करेगा जो sys.stdout प्रिंट नहीं sys.stdout और यह कि आपका अपना sys.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 द्वारा पहले ही किया जा चुका है, लेकिन मैं इन विकल्पों का अधिक विस्तार से पता लगाना चाहता था। विशेष रूप से मॉड्यूल ( __builtin__ / __builtin__ का उपयोग करके) में इसे कैसे संशोधित किया __builtin__ और उस परिवर्तन को केवल अस्थायी कैसे बनाया __builtin__ ( __builtin__ का उपयोग करके)।

नोट: यह प्रश्न केवल सूचना के उद्देश्यों के लिए है। मुझे यह देखने में दिलचस्पी है कि पायथन के इंटर्नल में कितना गहरा है, इसके साथ जाना संभव है।

बहुत समय पहले, एक निश्चित question के अंदर एक चर्चा शुरू हुई थी कि क्या प्रिंट स्टेटमेंट के लिए पारित किए गए कॉल को print करने के दौरान / उसके दौरान संशोधित किया जा सकता है। उदाहरण के लिए, फ़ंक्शन पर विचार करें:

def print_something():
    print('This cat was scared.')

अब, जब print चलाया जाता है, तो टर्मिनल को आउटपुट प्रदर्शित करना चाहिए:

This dog was scared.

"बिल्ली" शब्द को "कुत्ते" शब्द से बदल दिया गया है। कहीं न कहीं कुछ तो उन आंतरिक बफ़र्स को संशोधित करने में सक्षम था जो बदल गया था। मान लें कि यह मूल कोड लेखक की स्पष्ट अनुमति के बिना किया गया है (इसलिए, हैकिंग / अपहरण)।

विशेष रूप से, बुद्धिमान @abarnert की यह comment मुझे मिली:

ऐसा करने के कुछ तरीके हैं, लेकिन वे सभी बहुत बदसूरत हैं, और कभी नहीं किया जाना चाहिए। कम से कम बदसूरत तरीका संभवतः फ़ंक्शन के अंदर code ऑब्जेक्ट को एक अलग co_consts सूची के साथ co_consts । अगला शायद स्ट्रिंग के आंतरिक बफर तक पहुंचने के लिए सी एपीआई में पहुंच रहा है। [...]

तो, ऐसा लगता है कि यह वास्तव में संभव है।

यहाँ इस समस्या से निपटने का मेरा भोला तरीका है:

>>> import inspect
>>> exec(inspect.getsource(print_something).replace('cat', 'dog'))
>>> print_something()
This dog was scared.

बेशक, exec खराब है, लेकिन यह वास्तव में सवाल का जवाब नहीं देता है, क्योंकि यह वास्तव में print दौरान / बाद में कुछ भी संशोधित नहीं करता है।

यह कैसे किया जाएगा क्योंकि @abarnert ने इसे समझाया है?


एक print फ़ंक्शन से सभी आउटपुट को कैप्चर करने और फिर इसे प्रोसेस करने का एक सरल तरीका है, आउटपुट स्ट्रीम को किसी और चीज़ में बदलना, जैसे कि एक फ़ाइल।

मैं एक PHP नामकरण सम्मेलनों ( ob_start , ob_get_contents , ...) का उपयोग करूँगा

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"))

छपता होगा

हाय जॉन बाय जॉन


सबसे पहले, वहाँ वास्तव में एक बहुत कम hacky तरीका है। हम सब करना चाहते हैं क्या 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)

या, इसी तरह, आप print बजाय sys.stdout को sys.stdout कर सकते हैं।

इसके अलावा, exec … getsource … साथ कुछ भी गलत नहीं है exec … getsource … विचार। ठीक है, निश्चित रूप से इसके साथ बहुत गलत है, लेकिन यहाँ क्या है की तुलना में कम ...

लेकिन यदि आप फ़ंक्शन ऑब्जेक्ट के कोड कॉन्स्टेंट को संशोधित करना चाहते हैं, तो हम ऐसा कर सकते हैं।

यदि आप वास्तव में असली के लिए कोड ऑब्जेक्ट्स के साथ खेलना चाहते हैं, तो आपको मैन्युअल रूप से करने के बजाय byteplay (जब यह समाप्त हो जाता है) या byteplay (तब तक, या पुराने पायथन संस्करणों के लिए) जैसी लाइब्रेरी का उपयोग करना चाहिए। यहां तक ​​कि इस तुच्छ चीज के लिए भी, CodeType इनिशियलाइज़र एक दर्द है; अगर आपको वास्तव में lnotab ठीक करने जैसा सामान करने की आवश्यकता है, तो केवल एक lunatic ही ऐसा करेगा।

इसके अलावा, यह बिना कहे चला जाता है कि सभी पायथन कार्यान्वयन CPython- शैली कोड ऑब्जेक्ट का उपयोग नहीं करते हैं। यह कोड CPython 3.7 में काम करेगा, और शायद सभी संस्करण कुछ मामूली बदलावों के साथ कम से कम 2.2 में वापस आएंगे (और कोड-हैकिंग सामान नहीं, लेकिन जनरेटर के भाव जैसी चीजें), लेकिन यह आयरनपाइथन के किसी भी संस्करण के साथ काम नहीं करेगा।

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()

कोड वस्तुओं को हैक करने में क्या गलत हो सकता है? ज्यादातर सिर्फ segfaults, RuntimeError कि पूरे स्टैक को खाते हैं, अधिक सामान्य RuntimeError s जिसे संभाला जा सकता है, या कचरा मान जो कि शायद आपको उपयोग करने का प्रयास करते समय केवल एक TypeError या AttributeError उठाएगा। उदाहरण के लिए, केवल एक RETURN_VALUE साथ एक कोड ऑब्जेक्ट बनाने की कोशिश करें जिसमें स्टैक पर कुछ भी नहीं है ( RETURN_VALUE b'S\0' 3.6+ के लिए, b'S' पहले), या co_consts लिए एक खाली ट्यूपल के co_consts जब LOAD_CONST 0 में एक LOAD_CONST 0 है, या साथ varnames 1 से घटाया गया तो उच्चतम LOAD_FAST वास्तव में एक फ्रीवर / सेलवार सेल लोड करता है। कुछ वास्तविक मौज-मस्ती के लिए, यदि आपको lnotab काफी गलत मिलता है, तो आपका कोड केवल डिबगर में चलने पर lnotab होगा।

byteplay या byteplay का उपयोग करने से आप उन सभी समस्याओं से नहीं byteplay , लेकिन उनके पास कुछ बुनियादी पवित्रता byteplay , और अच्छे मददगार होते हैं, जो आपको कोड का एक हिस्सा डालने जैसी चीज़ों को करने देते हैं और सभी ऑफ़सेट्स और लेबल को अपडेट करने की चिंता करते हैं ताकि आप यह गलत नहीं हो सकता, और इसी तरह। (साथ ही, वे आपको उस हास्यास्पद 6-लाइन कंस्ट्रक्टर में टाइप करने के लिए रखते हैं, और ऐसा करने से आने वाले मूर्ख टाइपो को डीबग करने के लिए रखते हैं।)

अब # 2 पर।

मैंने उल्लेख किया है कि कोड ऑब्जेक्ट अपरिवर्तनीय हैं। और निश्चित रूप से कब्ज टपल हैं, इसलिए हम इसे सीधे नहीं बदल सकते। और const tuple में चीज़ एक स्ट्रिंग है, जिसे हम सीधे बदल भी नहीं सकते हैं। इसलिए मुझे नए कोड ऑब्जेक्ट बनाने के लिए एक नया टपल बनाने के लिए एक नया स्ट्रिंग बनाना था।

लेकिन क्या होगा अगर आप सीधे एक स्ट्रिंग बदल सकते हैं?

अच्छी तरह से, कवर के नीचे पर्याप्त गहरा, सब कुछ बस कुछ सी डेटा के लिए एक संकेतक है, है ना? यदि आप CPython का उपयोग कर रहे हैं, तो वस्तुओं तक पहुँचने के लिए एक C API है , और आप उस API को Python के भीतर से एक्सेस करने के लिए ctypes का उपयोग कर सकते हैं, जो कि इतना भयानक विचार है कि वे वहाँ stdlib के ctypes मॉड्यूल में एक pythonapi डालते हैं । :) सबसे महत्वपूर्ण चाल जिसे आपको जानना आवश्यक है, वह यह है कि id(x) मेमोरी में x से वास्तविक सूचक है (एक int रूप में)।

दुर्भाग्य से, तार के लिए सी एपीआई हमें पहले से जमे हुए स्ट्रिंग के आंतरिक भंडारण में सुरक्षित रूप से नहीं आने देगा। तो सुरक्षित रूप से पेंच, चलो हेडर फ़ाइलों को पढ़ते हैं और उस भंडारण को स्वयं ढूंढते हैं।

यदि आप CPython 3.4 - 3.7 का उपयोग कर रहे हैं (यह पुराने संस्करणों के लिए भिन्न है, और जो भविष्य के लिए जानता है), एक मॉड्यूल से एक स्ट्रिंग शाब्दिक जो शुद्ध ASCII से बना है, कॉम्पैक्ट ASCII प्रारूप का उपयोग करके संग्रहीत किया जाने वाला है, जिसका अर्थ है कि संरचना जल्दी समाप्त हो जाता है और ASCII बाइट्स का बफर स्मृति में तुरंत बाद में आता है। यह टूट जाएगा (जैसा कि शायद सेगफॉल्ट में) यदि आप स्ट्रिंग में एक गैर-एएससीआईआई चरित्र, या कुछ प्रकार के गैर-शाब्दिक तार डालते हैं, लेकिन आप विभिन्न प्रकार के तारों के लिए बफर तक पहुंचने के अन्य 4 तरीकों पर पढ़ सकते हैं।

चीजों को थोड़ा आसान बनाने के लिए, मैं अपने GitHub से superhackyinternals प्रोजेक्ट का उपयोग कर रहा हूं। (यह जानबूझकर पाइप-इंस्टॉल करने योग्य नहीं है क्योंकि आप वास्तव में इसका उपयोग नहीं कर रहे हैं सिवाय इंटरप्रेटर और इस तरह के अपने स्थानीय निर्माण के साथ प्रयोग करने के लिए।)

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()

यदि आप इस सामान के साथ खेलना चाहते हैं, तो str तुलना में int एक पूरी बहुत सरल है। और यह अनुमान लगाना बहुत आसान है कि आप 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 का मूल्यांकन करने की कोशिश की, तो यह किसी प्रकार के अबाधित अनंत लूप में चला गया। संभवतः यह अपने REPL लूप में कुछ के लिए नंबर 2 का उपयोग कर रहा है, जबकि स्टॉक दुभाषिया नहीं है?







python-internals