Repository: pandas-dev/pandas

Issue:
to_string() does not have an "encoding" parameter.
#### Code Sample, a copy-pastable example if possible

```python
df = pd.DataFrame(data = {'Japanese' : 'こんにちは', 'English' : 'Hello'}, index = [0])
df.to_string()

```

#### Output of ``pd.show_versions()``

<details>

[paste the output of ``pd.show_versions()`` here below this line]
INSTALLED VERSIONS
------------------
commit           : None
python           : 3.7.1.final.0
python-bits      : 64
OS               : Windows
OS-release       : 10
machine          : AMD64
processor        : Intel64 Family 6 Model 78 Stepping 3, GenuineIntel
byteorder        : little
LC_ALL           : None
LANG             : None
LOCALE           : None.None

pandas           : 0.25.0
numpy            : 1.16.2
pytz             : 2018.7
dateutil         : 2.7.5
pip              : 18.1
setuptools       : 40.6.3
Cython           : 0.29.2
pytest           : 4.0.2
hypothesis       : None
sphinx           : 1.8.2
blosc            : None
feather          : 0.4.0
xlsxwriter       : 1.1.2
lxml.etree       : 4.2.5
html5lib         : 1.0.1
pymysql          : None
psycopg2         : 2.8.3 (dt dec pq3 ext lo64)
jinja2           : 2.10
IPython          : 7.2.0
pandas_datareader: None
bs4              : 4.6.3
bottleneck       : 1.2.1
fastparquet      : None
gcsfs            : None
lxml.etree       : 4.2.5
matplotlib       : 3.0.2
numexpr          : 2.6.8
odfpy            : None
openpyxl         : 2.5.12
pandas_gbq       : None
pyarrow          : 0.13.0
pytables         : None
s3fs             : None
scipy            : 1.1.0
sqlalchemy       : 1.2.15
tables           : 3.4.4
xarray           : None
xlrd             : 1.2.0
xlwt             : 1.3.0
xlsxwriter       : 1.1.2

</details>



Discussion:
Can you give an example of the expected behavior and API parameters?
Just like to_csv() and to_excel() have an "encoding" parameters, we can have an encoding parameter in string for consistency. 

```python
df.to_string(encoding = "iso-8859-1")
```
Might not be thinking straight today but wouldn't this only make sense if it were a `to_bytes` method? Not applicable for `to_string`, no?
Agreed with Will. An encoding is only necessary when going between strings
and bytes. But we're converting from a Python object to a string.

On Thu, Oct 3, 2019 at 4:43 PM William Ayd <notifications@github.com> wrote:

> Might not be thinking straight today but wouldn't this only make sense if
> it were a to_bytes method? Not applicable for to_string, no?
>
> —
> You are receiving this because you are subscribed to this thread.
> Reply to this email directly, view it on GitHub
> <https://github.com/pandas-dev/pandas/issues/28766?email_source=notifications&email_token=AAKAOITGN7FSSWSDBQQ27R3QMZRRPA5CNFSM4I46HERKYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEAJWDTQ#issuecomment-538141134>,
> or mute the thread
> <https://github.com/notifications/unsubscribe-auth/AAKAOIVF6V2DFWHXZPJ7MYLQMZRRPANCNFSM4I46HERA>
> .
>

https://github.com/pandas-dev/pandas/issues/28663#issuecomment-536322605

>the to_html method has no encoding. I assume that it is fairly simple to work-around by passing an open buffer with the correct encoding or not passing a filepath, and writing the return unicode string to file using the appropriate encoding?

>the buffer handling code is shared with to_latex and to_string. to_latex already has an encoding parameter and the shared code can handle encoding. so it should be straightforward to add encoding to to_html ( and to_string) for consistency.
#15008 (comment) "Textual payloads should support the encoding argument"
Forgot about the `buf` argument accepting a string path.

In that case, accepting an `encoding` makes sense, for specifying the encoding for the newly created file. We should raise when encoding is specified and it's `buf` isn't a string path.

Resolving patch:
diff --git a/pandas/core/frame.py b/pandas/core/frame.py
--- a/pandas/core/frame.py
+++ b/pandas/core/frame.py
@@ -755,6 +755,7 @@ def to_string(
         decimal: str = ".",
         line_width: Optional[int] = None,
         max_colwidth: Optional[int] = None,
+        encoding: Optional[str] = None,
     ) -> Optional[str]:
         """
         Render a DataFrame to a console-friendly tabular output.
@@ -765,6 +766,10 @@ def to_string(
             Max width to truncate each column in characters. By default, no limit.
 
             .. versionadded:: 1.0.0
+        encoding : str, default "utf-8"
+            Set character encoding.
+
+            .. versionadded:: 1.0
         %(returns)s
         See Also
         --------
@@ -803,7 +808,7 @@ def to_string(
                 decimal=decimal,
                 line_width=line_width,
             )
-            return formatter.to_string(buf=buf)
+            return formatter.to_string(buf=buf, encoding=encoding)
 
     # ----------------------------------------------------------------------
 
diff --git a/pandas/io/formats/format.py b/pandas/io/formats/format.py
--- a/pandas/io/formats/format.py
+++ b/pandas/io/formats/format.py
@@ -485,6 +485,8 @@ def get_buffer(
 
         if encoding is None:
             encoding = "utf-8"
+        elif not isinstance(buf, str):
+            raise ValueError("buf is not a file name and encoding is specified.")
 
         if hasattr(buf, "write"):
             yield buf
@@ -895,8 +897,12 @@ def _join_multiline(self, *args) -> str:
             st = ed
         return "\n\n".join(str_lst)
 
-    def to_string(self, buf: Optional[FilePathOrBuffer[str]] = None) -> Optional[str]:
-        return self.get_result(buf=buf)
+    def to_string(
+        self,
+        buf: Optional[FilePathOrBuffer[str]] = None,
+        encoding: Optional[str] = None,
+    ) -> Optional[str]:
+        return self.get_result(buf=buf, encoding=encoding)
 
     def to_latex(
         self,


Test patch:
diff --git a/pandas/tests/io/formats/test_format.py b/pandas/tests/io/formats/test_format.py
--- a/pandas/tests/io/formats/test_format.py
+++ b/pandas/tests/io/formats/test_format.py
@@ -73,17 +73,19 @@ def filepath_or_buffer(filepath_or_buffer_id, tmp_path):
 
 
 @pytest.fixture
-def assert_filepath_or_buffer_equals(filepath_or_buffer, filepath_or_buffer_id):
+def assert_filepath_or_buffer_equals(
+    filepath_or_buffer, filepath_or_buffer_id, encoding
+):
     """
     Assertion helper for checking filepath_or_buffer.
     """
 
     def _assert_filepath_or_buffer_equals(expected):
         if filepath_or_buffer_id == "string":
-            with open(filepath_or_buffer) as f:
+            with open(filepath_or_buffer, encoding=encoding) as f:
                 result = f.read()
         elif filepath_or_buffer_id == "pathlike":
-            result = filepath_or_buffer.read_text()
+            result = filepath_or_buffer.read_text(encoding=encoding)
         elif filepath_or_buffer_id == "buffer":
             result = filepath_or_buffer.getvalue()
         assert result == expected
@@ -3240,14 +3242,32 @@ def test_repr_html_ipython_config(ip):
 
 
 @pytest.mark.parametrize("method", ["to_string", "to_html", "to_latex"])
+@pytest.mark.parametrize(
+    "encoding, data",
+    [(None, "abc"), ("utf-8", "abc"), ("gbk", "造成输出中文显示乱码"), ("foo", "abc")],
+)
 def test_filepath_or_buffer_arg(
-    float_frame, method, filepath_or_buffer, assert_filepath_or_buffer_equals
+    method,
+    filepath_or_buffer,
+    assert_filepath_or_buffer_equals,
+    encoding,
+    data,
+    filepath_or_buffer_id,
 ):
-    df = float_frame
-    expected = getattr(df, method)()
+    df = DataFrame([data])
 
-    getattr(df, method)(buf=filepath_or_buffer)
-    assert_filepath_or_buffer_equals(expected)
+    if filepath_or_buffer_id not in ["string", "pathlike"] and encoding is not None:
+        with pytest.raises(
+            ValueError, match="buf is not a file name and encoding is specified."
+        ):
+            getattr(df, method)(buf=filepath_or_buffer, encoding=encoding)
+    elif encoding == "foo":
+        with pytest.raises(LookupError, match="unknown encoding"):
+            getattr(df, method)(buf=filepath_or_buffer, encoding=encoding)
+    else:
+        expected = getattr(df, method)()
+        getattr(df, method)(buf=filepath_or_buffer, encoding=encoding)
+        assert_filepath_or_buffer_equals(expected)
 
 
 @pytest.mark.parametrize("method", ["to_string", "to_html", "to_latex"])