fstr 0.1.0a2
A library for performing delayed f-string evaluation.
License: MIT
Keywords: fstring,f-string

# fstr

<a href="https://travis-ci.org/rmorshea/fstr"><img alt="Build Status" src="https://travis-ci.org/rmorshea/fstr.svg?branch=master"></a>
<a href="https://pypi.org/project/fstr/"><img alt="PyPI" src="https://img.shields.io/pypi/v/fstr.svg"></a>
<a href="https://github.com/ambv/black"><img alt="Code style: black" src="https://img.shields.io/badge/code%20style-black-000000.svg"></a>
<a href="https://github.com/rmorshea/fstr/blob/master/LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/License-MIT-purple.svg"></a>

**1. Use f-string syntax in Python 2:**

```python
import fstr

x = 1
y = 2

template = fstr("{x} + {y} = {x + y}")

print(template.evaluate())
```

```
1 + 2 = 3
```

**2. Use f-string syntax instead of `str.format` in both Python 2 and 3:**

```python
import fstr

common_error_message = fstr("function {function.__name__!r} failed because {error}")

def add(x, y):
    try:
        return x + y
    except Exception as e:
        msg = common_error_message.format(function=add, error=e)
        print(msg)

def sub(x, y):
    try:
        return x + y
    except Exception as e:
        msg = common_error_message.format(function=sub, error=e)
        print(msg)

add(1, "2")
sub("5", 3)
```

```
function 'add' failed because unsupported operand type(s) for +: 'int' and 'str'
function 'sub' failed because can only concatenate str (not "int") to str
```


# Full [PEP-498](https://www.python.org/dev/peps/pep-0498) Compliance

Other backward compatibility libraries for f-string syntax in Python 2 only implement some of the capabilities defined in the PEP's [specification](https://www.python.org/dev/peps/pep-0498/#specification). The test cases for `fstr` were even lifted (with minor changes) from [CPython's test suite](https://github.com/python/cpython/blob/master/Lib/test/test_fstring.py).


## Format Specifiers

Format specifiers may contain evaluated expressions.

```python
import fstr
import decimal

width = 10
precision = 4
value = decimal.Decimal('12.34567')

fstr("result: {value:{width}.{precision}}").evaluate()
```

```
'result:      12.35'
```

Once expressions in a format specifier are evaluated (if necessary), format specifiers are not interpreted by the f-string evaluator. Just as in `str.format()`, they are merely passed in to the `__format__()` method of the object being formatted.


## Lambdas In Expressions

```python
import fstr

fstr("{(lambda x: x*2)(3)}").format()
```

```
'6'
```

## Error Handling

Exact messages will vary depending on whether you are using Python<3.6 or not.

---

```python
import fstr

fstr("x={x")
```

```
File "fstr", line 1
  x={x
      ^
SyntaxError: Mismatched braces in f-string.
```

---

```python
import fstr

fstr("x={!x}")
```

```
File "fstr", line 1
  x={!x}
    ^
SyntaxError: Empty expresion not allowed.
```


# Performance Considerations

`fstr` is not meant to be a replacement for python's f-string syntax. Rather it serves primarily as a slightly slower, but more convenient way to do string formatting in the
cases where you might otherwise use `str.format`. Additionally Python's f-string syntax is able to make performance optimizations at compile time that are not afforded to either `str.format` or `fstr.format`. Given this we only compare `fstr.format` to `str.format`.

The performance of `fstr` differs depending on whether you:

+ Use Python<3.6 or not.
+ Define your f-string template ahead of time.

For example, this will be **significantly** slower

```python
for i in range(10):
   s = fstr("{i}**2 = {i**2}").format(i=i)
```

than if you define your template outside the loop:

```python
template = fstr("{i}**2 = {i**2}")

for i in range(10):
   s = template.format(i=i)
```

## `str.format` vs `fstr.format`

```python
from timeit import timeit

str_setup = "template = '{x}' * 10"
fstr_setup = "import fstr\ntemplate = fstr('{x}' * 10)"

str_result = timeit("template.format(x=1)", setup=str_setup, number=1000000)
fstr_result = timeit("template.format(x=1)", setup=fstr_setup, number=1000000)

print("str.format() : %s seconds" % str_result)
print("fstr.format() : %s seconds" % fstr_result)
```

### Python < 3.6

```
str.format() : 0.741672992706 seconds
fstr.format() : 6.77992010117 seconds
```

### Python >= 3.6

```
str.format: 0.7007193689933047 seconds
fstr.format: 0.9083925349987112 seconds
```