Last active
March 7, 2017 17:55
-
-
Save danbradham/e5f15dc9346412fedc08fc1585d3292d to your computer and use it in GitHub Desktop.
f-strings backport...sorta
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- coding: utf-8 -*- | |
''' | |
f-strings backport...sorta | |
========================== | |
What it looks like:: | |
>>> x = 'Hello, World...' | |
>>> f('{x}') | |
'Hello, World...' | |
Features and Differences:: | |
- Uses str.format instead of allowing eval of python code in {} | |
- Allows overriding globals and locals by passing in *args and **kwargs | |
- Supports python 2.7 to python 3.5 | |
''' | |
import sys | |
from inspect import currentframe, getmro, isclass, isfunction | |
def f(s, *args, **kwargs): | |
''' | |
Sort of f-strings. Passes the calling frames globals and locals to | |
str.format instead of evaluating code in braces. Also supports positional | |
arguments and keyword arguments via str.format. If kwargs are passed in | |
they will take precedence over globals and locals. | |
:param args: Optional positional format args | |
:param kwargs: Optional format kwargs | |
:param _frame_: Optional kwarg - frame used to retrieve locals and globals | |
Usage:: | |
>>> x = "Hello, World!" | |
>>> f('{x}') | |
'Hello, World!' | |
''' | |
frame = kwargs.pop('_frame_', currentframe().f_back) | |
return s.format(*args, **dict(frame.f_globals, **dict(frame.f_locals, **kwargs))) | |
def fdocstring(*args, **kwargs): | |
''' | |
Formats the docstrings of the decorated function or class using globals | |
and locals. Also supports positional arguments and keyword arguments | |
via str.format. If kwargs are passed in they will take precedence over | |
globals and locals. Runs once at execution time, so it has ZERO impact | |
on the performance of your class or function. | |
:param args: Optional positional format args | |
:param kwargs: Option keyword format args | |
:param _frame_: Optional kwarg - frame used to retrieve locals and globals | |
Usage:: | |
>>> x = 'Hello from fdocstring' | |
>>> @fdocstring() | |
... def func(): | |
... \'\'\'{x}\'\'\' | |
... | |
>>> func.__doc__ | |
'Hello from fdocstring' | |
''' | |
def do_fdocstring(obj): | |
frame = kwargs.pop('_frame_', currentframe().f_back) | |
fkwargs = dict(frame.f_globals, **frame.f_locals) | |
if isfunction(obj): | |
obj.__doc__ = obj.__doc__.format(*args, **dict(fkwargs, **kwargs)) | |
if isclass(obj): | |
bases = getmro(obj) | |
for cls in bases[::-1]: | |
fkwargs.update(cls.__dict__) | |
fkwargs.update(**kwargs) | |
attrs = dict(obj.__dict__) | |
attrs['__doc__'] = obj.__doc__.format(*args, **fkwargs) | |
for k, v in attrs.items(): | |
if isfunction(v): | |
v.__doc__ = v.__doc__.format(*args, **fkwargs) | |
obj = type(obj.__name__, tuple(bases[1:]), attrs) | |
return obj | |
return do_fdocstring | |
def printf(s, *args, **kwargs): | |
''' | |
Write `f(s, *args, **kwargs)` to sys.stdout | |
:param _stdout_: Optional kwarg - Override output stream | |
Usage:: | |
>>> x = 'Hello from printf' | |
>>> printf('{x}') | |
Hello from printf | |
''' | |
kwargs['_frame_'] = currentframe().f_back | |
stdout = kwargs.pop('_stdout_', sys.stdout) | |
stdout.write(f(s + '\n', *args, **kwargs)) | |
def test(): | |
import doctest | |
sys.stdout.write('Running Doctests...............') | |
doctest.testmod() | |
sys.stdout.write('OK!\n') | |
sys.stdout.write('Running Tests..................') | |
# For testing purposes we manually assign to globals | |
# These are equivalent to module level global variables | |
globals()['URL'] = '/api/v1.0' | |
globals()['NUM'] = 10 | |
class OBJ: VAL = 20 | |
globals()['OBJ'] = OBJ | |
globals()['FS'] = f('{URL}/{NUM:0>3}/{OBJ.VAL}') | |
@fdocstring() | |
def test_function(): | |
'''{URL}/{NUM:0>3}/{OBJ.VAL}''' | |
@fdocstring() | |
class TestClass(object): | |
'''{URL}''' | |
URL = '/api/v1.1' | |
def test_method(self): | |
'''{URL}/method''' | |
def no_tokens(self): | |
'''No Tokens''' | |
@fdocstring() | |
class TestSubclassInherit(TestClass): | |
'''{URL}''' | |
def test_method(self): | |
'''{URL}/subclass_method''' | |
def test_method2(self): | |
'''{FS}''' | |
return f('{FS}/subclass_method') | |
@fdocstring(1, 2, URL='/api/v1.2', FS='WHAT') | |
class TestSubclassOverride(TestClass): | |
'''{} {} {URL}''' | |
def test_method(self): | |
'''{URL}/subclass_method''' | |
def test_method2(self): | |
'''{FS}''' | |
return f('{FS}/subclass_method') | |
def test_function2(): | |
a = 1 | |
b = 2 | |
c = 'funcy fresh' | |
return f('{a}, {b}, {c}') | |
def test_function3(): | |
a = 1 | |
b = 2 | |
c = 'funcy fresh' | |
return f('{a}, {b}, {c}', c='funky fresh') | |
assert FS == '/api/v1.0/010/20' | |
assert test_function.__doc__ == '/api/v1.0/010/20' | |
assert TestClass.__doc__ == '/api/v1.1' | |
assert TestClass.test_method.__doc__ == '/api/v1.1/method' | |
assert TestClass.no_tokens.__doc__ == 'No Tokens' | |
assert TestSubclassInherit.__doc__ == '/api/v1.1' | |
assert TestSubclassInherit.test_method.__doc__ == '/api/v1.1/subclass_method' | |
assert TestSubclassInherit.test_method2.__doc__ == '/api/v1.0/010/20' | |
assert TestSubclassInherit().test_method2() == '/api/v1.0/010/20/subclass_method' | |
assert TestSubclassOverride.__doc__ == '1 2 /api/v1.2' | |
assert TestSubclassOverride.test_method.__doc__ == '/api/v1.2/subclass_method' | |
assert TestSubclassOverride.test_method2.__doc__ == 'WHAT' | |
assert TestSubclassOverride().test_method2() == '/api/v1.0/010/20/subclass_method' | |
assert test_function2() == '1, 2, funcy fresh' | |
assert test_function3() == '1, 2, funky fresh' | |
sys.stdout.write('OK!\n') | |
sys.stdout.write('Running Benchmark..............') | |
from timeit import timeit | |
t = timeit( | |
"f('{a} {b} {c}')", | |
setup='from __main__ import f; a = 1; b = 2; c = 3', | |
number=100000 | |
) | |
sys.stdout.write('OK!\n') | |
printf("f('{{a}} {{b}} {{c}}') x 100000 took {}", t) | |
if __name__ == '__main__': | |
if sys.argv[1] == 'test': | |
test() | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
f-strings backport...sorta
What it looks like:
Features: