diff --git a/README.md b/README.md
index 12882ab3..1a508c83 100644
--- a/README.md
+++ b/README.md
@@ -145,45 +145,45 @@ which is automatically converted to a Julia type, you will have override this
 via `@pywith EXPR::PyObject ...`.
 
 If you are already familiar with Python, it perhaps is easier to use
-`py"..."` and `py"""..."""` which are equivalent to Python's
+`` py`...` `` and ` py```...``` ` which are equivalent to Python's
 [`eval`](https://docs.python.org/3/library/functions.html#eval) and
 [`exec`](https://docs.python.org/3/library/functions.html#exec),
 respectively:
 
-```julia
-py"""
+````julia
+py```
 import numpy as np
 
 def sinpi(x):
     return np.sin(np.pi * x)
-"""
-py"sinpi"(1)
 ```
+py`sinpi`(1)
+````
 
 When creating a Julia module, it is a useful pattern to define Python
 functions or classes in Julia's `__init__` and then use it in Julia
-function with `py"..."`.
+function with `` py`...` ``.
 
-```julia
+````julia
 module MyModule
 
 using PyCall
 
 function __init__()
-    py"""
+    py```
     import numpy as np
 
     def one(x):
         return np.sin(x) ** 2 + np.cos(x) ** 2
-    """
+    ```
 end
 
-two(x) = py"one"(x) + py"one"(x)
+two(x) = py`one`(x) + py`one`(x)
 
 end
-```
+````
 
-Note that Python code in `py"..."` of above example is evaluated in a
+Note that Python code in `` py`...` `` of above example is evaluated in a
 Python namespace dedicated to `MyModule`.  Thus, Python function `one`
 cannot be accessed outside `MyModule`.
 
@@ -355,38 +355,38 @@ and also by providing more type information to the Julia compiler.
   `@pycall function(args...)::returntype` into
   `pycall(function,returntype,args...)`.
 
-* `py"..."` evaluates `"..."` as Python code, equivalent to
+* `` py`...` `` evaluates `"..."` as Python code, equivalent to
   Python's [`eval`](https://docs.python.org/3/library/functions.html#eval) function, and returns the result
-  converted to `PyAny`.  Alternatively, `py"..."o` returns the raw `PyObject`
+  converted to `PyAny`.  Alternatively, `` py`...`o `` returns the raw `PyObject`
   (which can then be manually converted if desired).   You can interpolate
   Julia variables and other expressions into the Python code with `$`,
   which interpolates the *value* (converted to `PyObject`) of the given
   expression---data is not passed as a string, so this is different from
-  ordinary Julia string interpolation.  e.g. `py"sum($([1,2,3]))"` calls the
+  ordinary Julia string interpolation.  e.g. `` py`sum($([1,2,3]))` `` calls the
   Python `sum` function on the Julia array `[1,2,3]`, returning `6`.
   In contrast, if you use `$$` before the interpolated expression, then
   the value of the expression is inserted as a string into the Python code,
   allowing you to generate Python code itself via Julia expressions.
-  For example, if `x="1+1"` in Julia, then `py"$x"` returns the string `"1+1"`,
-  but `py"$$x"` returns `2`.
-  If you use `py"""..."""` to pass a *multi-line* string, the string can
+  For example, if `x="1+1"` in Julia, then `` py`$x` `` returns the string `"1+1"`,
+  but `` py`$$x` `` returns `2`.
+  If you use ` py```...``` ` to pass a *multi-line* string, the string can
   contain arbitrary Python code (not just a single expression) to be evaluated,
   but the return value is `nothing`; this is useful e.g. to define pure-Python
   functions, and is equivalent to Python's
   [`exec`](https://docs.python.org/3/library/functions.html#exec) function.
-  (If you define a Python global `g` in a multiline `py"""..."""`
-  string, you can retrieve it in Julia by subsequently evaluating `py"g"`.)
+  (If you define a Python global `g` in a multiline ` py```...``` `
+  string, you can retrieve it in Julia by subsequently evaluating `` py`g` ``.)
 
-  When `py"..."` is used inside a Julia module, it uses a Python namespace
+  When `` py`...` `` is used inside a Julia module, it uses a Python namespace
   dedicated to this Julia module.  Thus, you can define Python function
-  using `py"""...."""` in your module without worrying about name clash
+  using ` py```....``` ` in your module without worrying about name clash
   with other Python code.  Note that Python functions _must_ be defined in
   `__init__`.  Side-effect in Python occurred at top-level Julia scope
   cannot be used at run-time for precompiled modules.
 
 * `pybuiltin(s)`: Look up `s` (a string or symbol) among the global Python
   builtins.  If `s` is a string it returns a `PyObject`, while if `s` is a
-  symbol it returns the builtin converted to `PyAny`.  (You can also use `py"s"`
+  symbol it returns the builtin converted to `PyAny`.  (You can also use `` py`s` ``
   to look up builtins or other Python globas.)
 
 Occasionally, you may need to pass a keyword argument to Python that
diff --git a/src/PyCall.jl b/src/PyCall.jl
index 76bb5735..868c3a87 100644
--- a/src/PyCall.jl
+++ b/src/PyCall.jl
@@ -13,8 +13,8 @@ export pycall, pycall!, pyimport, pyimport_e, pybuiltin, PyObject, PyReverseDims
        pyisinstance, pywrap, pytypeof, pyeval, PyVector, pystring, pystr, pyrepr,
        pyraise, pytype_mapping, pygui, pygui_start, pygui_stop,
        pygui_stop_all, @pylab, set!, PyTextIO, @pysym, PyNULL, ispynull, @pydef,
-       pyimport_conda, @py_str, @pywith, @pycall, pybytes, pyfunction, pyfunctionret,
-       pywrapfn, pysetarg!, pysetargs!
+       pyimport_conda, @py_cmd, @py_str, @pywith, @pycall, pybytes, pyfunction,
+       pyfunctionret, pywrapfn, pysetarg!, pysetargs!
 
 import Base: size, ndims, similar, copy, getindex, setindex!, stride,
        convert, pointer, summary, convert, show, haskey, keys, values,
diff --git a/src/pyeval.jl b/src/pyeval.jl
index 4eca19d5..adc026e4 100644
--- a/src/pyeval.jl
+++ b/src/pyeval.jl
@@ -53,7 +53,7 @@ For example, `pyeval("x + y", x=1, y=2)` returns 3.
 function pyeval(s::AbstractString, returntype::TypeTuple=PyAny,
                 locals=PyDict{AbstractString, PyObject}(),
                 input_type=Py_eval_input; kwargs...)
-    # construct deprecation warning in favor of py"..." strings
+    # construct deprecation warning in favor of py`...` strings
     depbuf = IOBuffer()
     q = input_type==Py_eval_input ? "\"" : "\"\"\"\n"
     qr = reverse(q)
@@ -177,17 +177,18 @@ function interpolate_pycode(code::AbstractString)
 end
 
 """
-    py".....python code....."
+    py`.....python code.....`[o]
+    py".....python code....."[o]
 
-Evaluate the given Python code string in the main Python module.
+Evaluate the given Python code in the main Python module.
 
-If the string is a single line (no newlines), then the Python
+If the input is a single line (no newlines), then the Python
 expression is evaluated and the result is returned.
-If the string is multiple lines (contains a newline), then the Python
+If the input has multiple lines (contains a newline), then the Python
 code is compiled and evaluated in the `__main__` Python module
 and nothing is returned.
 
-If the `o` option is appended to the string, as in `py"..."o`, then the
+If the `o` option is appended to the command, as in ``` py`...`o ```, then the
 return value is an unconverted `PyObject`; otherwise, it is
 automatically converted to a native Julia type if possible.
 
@@ -196,12 +197,21 @@ Any `\$var` or `\$(expr)` expressions that appear in the Python code
 and passed to Python via auto-generated global variables. This
 allows you to "interpolate" Julia values into Python code.
 
-Similarly, ny `\$\$var` or `\$\$(expr)` expressions in the Python code
+Similarly, `\$\$var` or `\$\$(expr)` expressions in the Python code
 are evaluated in Julia, converted to strings via `string`, and are
-pasted into the Python code.   This allows you to evaluate code
+pasted into the Python code. This allows you to evaluate code
 where the code itself is generated by a Julia expression.
 """
+macro py_cmd(code, options...)
+    return py_cmd(__module__, code, options...)
+end
+
+@doc (@doc @py_cmd)
 macro py_str(code, options...)
+    return py_cmd(__module__, code, options...)
+end
+
+function py_cmd(__module__, code, options...)
     T = length(options) == 1 && 'o' in options[1] ? PyObject : PyAny
     code, locals = interpolate_pycode(code)
     input_type = '\n' in code ? Py_file_input : Py_eval_input
diff --git a/test/runtests.jl b/test/runtests.jl
index eef1360e..4f56c2b4 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -155,7 +155,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
 
     # issue #92:
     let x = PyVector(PyAny[])
-        py"lambda x: x.append(\"bar\")"(x)
+        py`lambda x: x.append("bar")`(x)
         @test x == ["bar"]
     end
 
@@ -267,21 +267,21 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
     @test convert(BigInt, PyObject(1234)) == 1234
 
     # hasproperty, getproperty, and propertynames
-    py"""
+    py```
     class A:
         class B:
             C = 1
-    """
-    A = py"A"
+    ```
+    A = py`A`
     @test hasproperty(A, "B")
-    @test getproperty(A, "B") == py"A.B"
+    @test getproperty(A, "B") == py`A.B`
     @test :B in propertynames(A)
     @static if VERSION >= v"0.7-"
         @test A.B.C == 1
         @test_throws KeyError A.X
     end
-    setproperty!(py"A.B", "C", 2)
-    @test py"A.B.C" == 2
+    setproperty!(py`A.B`, "C", 2)
+    @test py`A.B.C` == 2
 
     # buffers
     let b = PyCall.PyBuffer(pyutf8("test string"))
@@ -373,23 +373,23 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
     end
 
     let x = 7
-        py"""
+        py```
         def myfun(x):
             return x + $x
-        """
-        @test py"1 + 2" == 3
-        @test py"1 + $x" == 8
-        @test py"1 + $(x^2)" == 50
-        @test py"myfun"(10) == 17
+        ```
+        @test py`1 + 2` == 3
+        @test py`1 + $x` == 8
+        @test py`1 + $(x^2)` == 50
+        @test py`myfun`(10) == 17
     end
 
     # issue #352
     let x = "1+1"
-        @test py"$x" == "1+1"
-        @test py"$$x" == py"$$(x)" == 2
-        @test py"7 - $$x - 7" == 0 # evaluates "7 - 1 + 1 - 7"
-        @test py"7 - ($$x) - 7" == -2 # evaluates "7 - (1 + 1) - 7"
-        @test py"1 + $$(x[1:2]) 3" == 5 # evals 1 + 1+ 3
+        @test py`$x` == "1+1"
+        @test py`$$x` == py`$$(x)` == 2
+        @test py`7 - $$x - 7` == 0 # evaluates "7 - 1 + 1 - 7"
+        @test py`7 - ($$x) - 7` == -2 # evaluates "7 - (1 + 1) - 7"
+        @test py`1 + $$(x[1:2]) 3` == 5 # evals 1 + 1+ 3
     end
 
     # Float16 support:
@@ -429,15 +429,15 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
     end
 
     # Expose python docs to Julia doc system
-    py"""
+    py```
     def foo():
         "foo docstring"
         return 0
     class bar:
         foo = foo
-    """
-    global foo354 = py"foo"
-    global barclass = py"bar"
+    ```
+    global foo354 = py`foo`
+    global barclass = py`bar`
     # use 'content' since `Text` objects test equality by object identity
     @test @doc(foo354).content == "foo docstring"
     @test @doc(barclass.foo).content == "foo docstring"
@@ -518,7 +518,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
     end
 
     @test occursin("integer", Base.Docs.doc(PyObject(1)).content)
-    @test occursin("no docstring", Base.Docs.doc(PyObject(py"lambda x: x+1")).content)
+    @test occursin("no docstring", Base.Docs.doc(PyObject(py`lambda x: x+1`)).content)
 
     let b = rand(UInt8, 1000)
         @test(convert(Vector{UInt8}, pybytes(b)) == b
@@ -555,9 +555,9 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
     @test_throws KeyError PyObject(TestConstruct(1)).y
 
     # iterating over Julia objects in Python:
-    @test py"[x**2 for x in $(PyCall.pyjlwrap_new(1:4))]" ==
-          py"[x**2 for x in $(x for x in 1:4)]" ==
-          py"[x**2 for x in $(PyCall.jlwrap_iterator(1:4))]" ==
+    @test py`[x**2 for x in $(PyCall.pyjlwrap_new(1:4))]` ==
+          py`[x**2 for x in $(x for x in 1:4)]` ==
+          py`[x**2 for x in $(PyCall.jlwrap_iterator(1:4))]` ==
           [1,4,9,16]
 
     let o = PyObject("foo")
@@ -580,7 +580,7 @@ const PyInt = pyversion < v"3" ? Int : Clonglong
     end
 
     # issue #533
-    @test py"lambda x,y,z: (x,y,z)"(3:6,4:10,5:11) === (PyInt(3):PyInt(6), PyInt(4):PyInt(10), PyInt(5):PyInt(11))
+    @test py`lambda x,y,z: (x,y,z)`(3:6,4:10,5:11) === (PyInt(3):PyInt(6), PyInt(4):PyInt(10), PyInt(5):PyInt(11))
 
     @test float(PyObject(1)) === 1.0
     @test float(PyObject(1+2im)) === 1.0 + 2.0im
@@ -650,12 +650,12 @@ end
             using PyCall
             obj = pyimport("sys")  # get some PyObject
         end)
-    py"""
+    py```
     ns = {}
     def set(name):
         ns[name] = $include_string($anonymous, name)
-    """
-    py"set"("obj")
+    ```
+    py`set`("obj")
     @test anonymous.obj != PyNULL()
 
     # Test above for pyjlwrap_getattr too:
@@ -668,12 +668,12 @@ end
             end
             obj = S(pyimport("sys"))
         end)
-    py"""
+    py```
     ns = {}
     def set(name):
         ns[name] = $include_string($anonymous, name).x
-    """
-    py"set"("obj")
+    ```
+    py`set`("obj")
     @test anonymous.obj.x != PyNULL()
 
     # Test above for pyjlwrap_iternext too:
@@ -684,12 +684,12 @@ end
             sys = pyimport("sys")
             obj = (sys for _ in 1:1)
         end)
-    py"""
+    py```
     ns = {}
     def set(name):
         ns[name] = list(iter($include_string($anonymous, name)))
-    """
-    py"set"("obj")
+    ```
+    py`set`("obj")
     @test anonymous.sys != PyNULL()
 end
 
@@ -697,28 +697,28 @@ struct Unprintable end
 Base.show(::IO, ::Unprintable) = error("show(::IO, ::Unprintable) called")
 Base.show(::IO, ::Type{Unprintable}) = error("show(::IO, ::Type{Unprintable}) called")
 
-py"""
+py```
 def try_repr(x):
     try:
         return repr(x)
     except Exception as err:
         return err
-"""
+```
 
-py"""
+py```
 def try_call(f):
     try:
         return f()
     except Exception as err:
         return err
-"""
+```
 
 @testset "throwing show" begin
     unp = Unprintable()
     @test_throws Exception show(unp)
-    @test py"try_repr"("printable") isa String
-    @test pyisinstance(py"try_repr"(unp), pybuiltin("Exception"))
-    @test pyisinstance(py"try_call"(() -> throw(Unprintable())),
+    @test py`try_repr`("printable") isa String
+    @test pyisinstance(py`try_repr`(unp), pybuiltin("Exception"))
+    @test pyisinstance(py`try_call`(() -> throw(Unprintable())),
                        pybuiltin("Exception"))
 end
 
@@ -749,25 +749,25 @@ end
     @test Base.IteratorSize(PyCall.PyIterator(PyObject([1]))) == Base.HasLength()
 
     # 594
-    @test collect(zip(py"iter([1, 2, 3])", 1:3)) ==
+    @test collect(zip(py`iter([1, 2, 3])`, 1:3)) ==
     [(1, 1), (2, 2), (3, 3)]
-    @test collect(zip(PyCall.PyIterator{Int}(py"iter([1, 2, 3])"), 1:3)) ==
+    @test collect(zip(PyCall.PyIterator{Int}(py`iter([1, 2, 3])`), 1:3)) ==
     [(1, 1), (2, 2), (3, 3)]
-    @test collect(zip(PyCall.PyIterator(py"[1, 2, 3]"o), 1:3)) ==
+    @test collect(zip(PyCall.PyIterator(py`[1, 2, 3]`o), 1:3)) ==
     [(1, 1), (2, 2), (3, 3)]
 end
 
-@test_throws PyCall.PyError py"__testing_pynamespace"
+@test_throws PyCall.PyError py`__testing_pynamespace`
 
 module __isolated_namespace
 using PyCall
-py"""
+py```
 __testing_pynamespace = True
-"""
-get_testing_pynamespace() = py"__testing_pynamespace"
+```
+get_testing_pynamespace() = py`__testing_pynamespace`
 end
 
-@test_throws PyCall.PyError py"__testing_pynamespace"
+@test_throws PyCall.PyError py`__testing_pynamespace`
 @test __isolated_namespace.get_testing_pynamespace()
 
 @testset "atexit" begin
diff --git a/test/test_pyfncall.jl b/test/test_pyfncall.jl
index 87374bb7..a67ed3fe 100644
--- a/test/test_pyfncall.jl
+++ b/test/test_pyfncall.jl
@@ -1,13 +1,13 @@
 using Test, PyCall
 
-py"""
+py```
 def mklist(*args, **kwargs):
     l = list(args)
     l.extend(kwargs.items())
     return l
-"""
+```
 @testset "pycall!" begin
-    pymklist = py"mklist"
+    pymklist = py`mklist`
     ret = PyNULL()
 
     function pycall_checks(res, pyf, RetType, args...; kwargs...)