Repository: pri22296/beautifultable

Issue:
Misaligned when using colored strings via module Blessings
The printed table is misaligned when using colored data via module Blessings.
Here is an example.

```
from beautifultable import BeautifulTable
import blessings

term = blessings.Terminal()

table = BeautifulTable(max_width=120)

table.column_headers = [term.green('Header A'), term.cyan('Header B')]
table.append_row([term.yellow('data'), term.cyan('data')])

print(table)
```
Tested on:
- Python 3.6.7 (default, Oct 22 2018, 11:32:17)

Screenshot of output:
![image](https://user-images.githubusercontent.com/1115421/52619111-e0b6f600-2ec6-11e9-9269-8529709a1b44.png)





Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/pri22296/beautifultable/pull/91?src=pr&el=h1) Report
> Merging [#91](https://codecov.io/gh/pri22296/beautifultable/pull/91?src=pr&el=desc) into [master](https://codecov.io/gh/pri22296/beautifultable/commit/5eac2dbb213ce5f35b05be87c1562c0fb984cdf3&el=desc) will **increase** coverage by `0.36%`.
> The diff coverage is `78.47%`.

[![Impacted file tree graph](https://codecov.io/gh/pri22296/beautifultable/pull/91/graphs/tree.svg?width=650&height=150&src=pr&token=JGEoYMIaay)](https://codecov.io/gh/pri22296/beautifultable/pull/91?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master      #91      +/-   ##
==========================================
+ Coverage   79.76%   80.12%   +0.36%     
==========================================
  Files          12       10       -2     
  Lines        1028     1258     +230     
  Branches      164      186      +22     
==========================================
+ Hits          820     1008     +188     
- Misses        154      190      +36     
- Partials       54       60       +6     
```


| [Impacted Files](https://codecov.io/gh/pri22296/beautifultable/pull/91?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [beautifultable/\_\_init\_\_.py](https://codecov.io/gh/pri22296/beautifultable/pull/91/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvX19pbml0X18ucHk=) | `100.00% <ø> (ø)` | |
| [beautifultable/beautifultable.py](https://codecov.io/gh/pri22296/beautifultable/pull/91/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvYmVhdXRpZnVsdGFibGUucHk=) | `75.51% <77.04%> (+1.29%)` | :arrow_up: |
| [beautifultable/base.py](https://codecov.io/gh/pri22296/beautifultable/pull/91/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvYmFzZS5weQ==) | `80.45% <79.36%> (+14.45%)` | :arrow_up: |
| [beautifultable/\_\_version\_\_.py](https://codecov.io/gh/pri22296/beautifultable/pull/91/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvX192ZXJzaW9uX18ucHk=) | `100.00% <100.00%> (ø)` | |
| [beautifultable/meta.py](https://codecov.io/gh/pri22296/beautifultable/pull/91/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvbWV0YS5weQ==) | `69.56% <100.00%> (ø)` | |
| [beautifultable/utils.py](https://codecov.io/gh/pri22296/beautifultable/pull/91/diff?src=pr&el=tree#diff-YmVhdXRpZnVsdGFibGUvdXRpbHMucHk=) | `89.23% <100.00%> (+14.23%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/pri22296/beautifultable/pull/91?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/pri22296/beautifultable/pull/91?src=pr&el=footer). Last update [5eac2db...ec68330](https://codecov.io/gh/pri22296/beautifultable/pull/91?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.coveragerc b/.coveragerc
index d766443..ea8ef7d 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -8,6 +8,7 @@ exclude_lines =
     pragma: no cover
     raise NotImplementedError
     if __name__ == .__main__.:
+    pass
 ignore_errors = True
 omit =
     test.py
diff --git a/.gitignore b/.gitignore
index 87e4a82..ca16626 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ build/
 *.pyc
 .coverage
 .vscode/
+.venv
diff --git a/.travis.yml b/.travis.yml
index eb7005b..5a0684a 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -12,6 +12,10 @@ matrix:
       dist: xenial
       sudo: true
       env: TOXENV=py37
+    - python: 3.8
+      dist: xenial
+      sudo: true
+      env: TOXENV=py38
     - python: pypy3
       env: TOXENV=pypy3
     - python: 3.4
diff --git a/README.rst b/README.rst
index bbb147b..f1aedf6 100644
--- a/README.rst
+++ b/README.rst
@@ -80,12 +80,13 @@ Here is an example of how you can use beautifultable::
 
     >>> from beautifultable import BeautifulTable
     >>> table = BeautifulTable()
-    >>> table.column_headers = ["name", "rank", "gender"]
-    >>> table.append_row(["Jacob", 1, "boy"])
-    >>> table.append_row(["Isabella", 1, "girl"])
-    >>> table.append_row(["Ethan", 2, "boy"])
-    >>> table.append_row(["Sophia", 2, "girl"])
-    >>> table.append_row(["Michael", 3, "boy"])
+    >>> table.rows.append(["Jacob", 1, "boy"])
+    >>> table.rows.append(["Isabella", 1, "girl"])
+    >>> table.rows.append(["Ethan", 2, "boy"])
+    >>> table.rows.append(["Sophia", 2, "girl"])
+    >>> table.rows.append(["Michael", 3, "boy"])
+    >>> table.rows.header = ["S1", "S2", "S3", "S4", "S5"]
+    >>> table.columns.header = ["name", "rank", "gender"]
     >>> print(table)
     +----------+------+--------+
     |   name   | rank | gender |
@@ -115,7 +116,7 @@ Installation
 
 ::
 
-    pip install beautifultable
+    python3 -m pip install beautifultable
 
 .. inclusion-marker-install-end
 
@@ -128,9 +129,40 @@ Changelog
 **************************************************************************
 
 ==========
-Unreleased
+v1.0.0
 ==========
 
+* Added two new views ``rows`` and ``columns`` to the ``BeautifulTable`` class. Most of the existing
+  methods have been deprecated. Methods of the form ``{}_row`` and ``{}_column`` have been moved to
+  views ``rows.{}`` and ``columns.{}``(ex. ``append_row`` is now ``rows.append``). Calling older
+  deprecated methods will now raise a ``FutureWarning``. Special methods such as ``__len__``, ``__iter__``,
+  etc. have also been moved to the respective views. For details, refer the
+  API documentation and the Updated Tutorial
+* Added support for row headers. As a result rows can now be accessed by their keys similar
+  to columns
+* Added two new methods ``to_csv`` and ``from_csv`` to directly export/import using a csv file. (Thanks to @dinko-pehar)
+* Added ``filter`` method to generate a new table with only certain rows
+* Added a new ``shape`` attribute to the ``BeautifulTable`` class which returns a tuple of form (nrow, ncol)
+* Added alignment attribute to the ``BeautifulTable.columns.header`` object which can be used to
+  have a seperate header alignment. The default behaviour is to inherit ``BeautifulTable.columns.header``
+* Updated ``sort`` method to now also accept any callables as a key.
+* Updated behaviour of ``BeautifulTable.columns.width`` (earlier ``BeautifulTable.column_widths``).
+  It no longer overrides user specified widths by default. You can reset it to default
+  by setting it to **"auto"**
+* Deprecated attribute ``serialno`` and ``serialno_header``. User can now easily implement
+  this functionality by using row headers if required
+* Deprecated method ``get_table_width()``. You can now access the ``BeautifulTable.width`` attribute
+  to get the same result.
+* Deprecated constructor arguments and class attributes named ``sign_mode``, ``numeric_precision``,
+  ``max_width`` and renamed to ``sign``, ``precision`` and ``maxwidth`` respectively
+* Fixed an issue where table was malformed if ``blessings`` was used to generate colored strings.
+* Fixed issues with the existing implementation of ``__iter__``, ``__copy__`` and ``__deepcopy__`` which
+  should now work more reliably
+* Fixed an issue where default padding could not be set to 0. (Thanks to @furlongm)
+* Fixed several memory leak issues by ensuring that all internal objects hold only a weak reference
+  to the table instance.
+* Dropped support for Python 2
+
 ==========
 v0.8.0
 ==========
diff --git a/beautifultable/__init__.py b/beautifultable/__init__.py
index b4f3509..d35719c 100644
--- a/beautifultable/__init__.py
+++ b/beautifultable/__init__.py
@@ -2,16 +2,19 @@ from .__version__ import __title__, __description__, __url__, __version__
 from .__version__ import __copyright__, __author__, __author_email__
 from .__version__ import __license__
 
-from .beautifultable import BeautifulTable
+from .beautifultable import (  # noqa F401
+    BeautifulTable,
+    BTRowCollection,
+    BTColumnCollection,
+    BTRowHeader,
+    BTColumnHeader,
+    __all__,
+)
 from . import enums
 from .enums import *  # noqa
-from .exceptions import BeautifulTableDeprecationWarning
 
-import warnings
 
-
-__all__ = [
-    "BeautifulTable",
+__all__ = __all__ + [
     "__title__",
     "__description__",
     "__url__",
@@ -34,6 +37,3 @@ for token in dir(enums):
     ):
         setattr(BeautifulTable, token, getattr(enums, token))
         __all__.append(token)
-
-
-warnings.simplefilter("always", BeautifulTableDeprecationWarning)
diff --git a/beautifultable/__version__.py b/beautifultable/__version__.py
index ade5dd8..7cd0cc4 100644
--- a/beautifultable/__version__.py
+++ b/beautifultable/__version__.py
@@ -1,8 +1,8 @@
 __title__ = "beautifultable"
 __description__ = "Print ASCII tables for terminals"
 __url__ = "https://github.com/pri22296/beautifultable"
-__version__ = "0.8.0"
+__version__ = "1.0.0"
 __author__ = "Priyam Singh"
 __author_email__ = "priyamsingh.22296@gmail.com"
 __license__ = "MIT"
-__copyright__ = "Copyright 2019 Priyam Singh"
+__copyright__ = "Copyright 2020 Priyam Singh"
diff --git a/beautifultable/ansi.py b/beautifultable/ansi.py
index 1fda2eb..db95851 100644
--- a/beautifultable/ansi.py
+++ b/beautifultable/ansi.py
@@ -15,7 +15,9 @@ from .compat import to_unicode
 
 class ANSIMultiByteString(object):
 
-    ANSI_REGEX = re.compile(r"(\x1B\[[0-?]*[ -/]*[@-~])")
+    ANSI_REGEX = re.compile(
+        r"(\x1B(?:[()][AB012]|[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))"
+    )
     ANSI_RESET = "\x1b[0m"
 
     def __init__(self, string):
diff --git a/beautifultable/base.py b/beautifultable/base.py
index 7d12440..e357575 100644
--- a/beautifultable/base.py
+++ b/beautifultable/base.py
@@ -1,23 +1,36 @@
-from .compat import basestring
+import abc
+import weakref
 
 
-class BaseRow(object):
-    def __init__(self, table, row):
-        self._row = list(row)
+class BTBaseList(object, metaclass=abc.ABCMeta):
+    def __init__(self, table, value):
         self._table = table
+        self._value = self._validate(list(value))
+
+    @property
+    def _table(self):
+        return self._table_ref()
+
+    @_table.setter
+    def _table(self, value):
+        self._table_ref = weakref.ref(value)
+
+    @property
+    def value(self):
+        return self._value
 
     def __len__(self):
-        return len(self._row)
+        return len(self._value)
 
     def __iter__(self):
-        return iter(self._row)
+        return iter(self._value)
 
     def __next__(self):
-        return next(self._row)
+        return next(self._value)
 
     def __repr__(self):
         class_ = type(self).__name__
-        data = ", ".join(str(v) for v in self._row)
+        data = ", ".join(repr(v) for v in self._value)
         return "{}<{}>".format(class_, data)
 
     def __eq__(self, other):
@@ -28,48 +41,91 @@ class BaseRow(object):
                 return False
         return True
 
+    def __contains__(self, item):
+        """Returns whether `item` is present"""
+        return item in self._value
+
     def _append(self, item):
-        self._row.append(item)
+        self._value.append(item)
 
     def _insert(self, i, item):
-        self._row.insert(i, item)
+        self._value.insert(i, item)
 
     def _pop(self, i=-1):
-        return self._row.pop(i)
+        return self._value.pop(self._get_canonical_key(i))
 
     def _remove(self, item):
-        self._row.remove(item)
+        self._value.remove(item)
+
+    def _reverse(self):
+        self._value.reverse()
+
+    def _sort(self, key, reverse=False):
+        self._value.sort(key=key, reverse=reverse)
 
     def _clear(self):
-        self._row.clear()
+        self._value.clear()
 
     def count(self, item):
-        return self._row.count(item)
+        return self._value.count(item)
 
     def index(self, item, *args):
-        return self._row.index(item, *args)
+        """Returns the index of `item`"""
+        try:
+            return self._value.index(item, *args)
+        except ValueError:
+            raise KeyError("Key {} is not available".format(item))
 
     def __getitem__(self, key):
-        if isinstance(key, (int, slice)):
-            return self._row[key]
-        if isinstance(key, basestring):
-            index = self._table.get_column_index(key)
-            return self._row[index]
-        raise TypeError(
-            ("row indices must be integers or slices, " "not {}").format(
-                type(key).__name__
-            )
-        )
+        """Returns item at index or header `key`"""
+        return self._value[self._get_canonical_key(key)]
 
     def __setitem__(self, key, value):
-        if isinstance(key, int):
-            self._row[key] = value
-        elif isinstance(key, basestring):
-            index = self._table.get_column_index(key)
-            self._row[index] = value
-        else:
-            raise TypeError(
-                ("row indices must be integers or slices, " "not {}").format(
-                    type(key).__name__
+        """Updates item at index or header `key`"""
+        self._value[self._get_canonical_key(key)] = value
+
+    def __delitem__(self, key):
+        del self._value[self._get_canonical_key(key)]
+
+    def _validate(self, value):
+        if len(value) != self._get_ideal_length():
+            raise ValueError(
+                ("'Expected iterable of length {}, " "got {}").format(
+                    self._get_ideal_length(), len(value)
                 )
             )
+        return value
+
+    @abc.abstractmethod
+    def _get_canonical_key(self, key):
+        pass
+
+    @abc.abstractmethod
+    def _get_ideal_length(self):
+        pass
+
+
+class BTBaseRow(BTBaseList):
+    def _get_canonical_key(self, key):
+        return self._table.columns._canonical_key(key)
+
+    def _get_ideal_length(self):
+        return self._table._ncol
+
+    def _validate(self, value):
+        if self._get_ideal_length() == 0 and len(value) > 0:
+            self._table.columns._reset_state(len(value))
+        return super(BTBaseRow, self)._validate(value)
+
+
+class BTBaseColumn(BTBaseList):
+    def _get_canonical_key(self, key):
+        return self._table.rows._canonical_key(key)
+
+    def _get_ideal_length(self):
+        return len(self._table._data)
+
+    def _validate(self, value):
+        if self._get_ideal_length() == 0 and len(value) > 0:
+            self._table.rows._reset_state(len(value))
+        return super(BTBaseColumn, self)._validate(value)
diff --git a/beautifultable/beautifultable.py b/beautifultable/beautifultable.py
index 89edd8f..c656970 100644
--- a/beautifultable/beautifultable.py
+++ b/beautifultable/beautifultable.py
@@ -29,22 +29,45 @@ from __future__ import division, unicode_literals
 
 import copy
 import csv
-import operator
 
 from . import enums
 
-from .utils import get_output_str, raise_suppressed, termwidth, deprecation
-from .rows import RowData, HeaderData
-from .meta import AlignmentMetaData, PositiveIntegerMetaData
+from .utils import pre_process, termwidth, deprecated, deprecated_param
 from .compat import basestring, Iterable, to_unicode
+from .base import BTBaseList
+from .helpers import (
+    BTRowCollection,
+    BTColumnCollection,
+    BTRowHeader,
+    BTColumnHeader,
+)
 
 
-__all__ = ["BeautifulTable"]
+__all__ = [
+    "BeautifulTable",
+    "BTRowCollection",
+    "BTColumnCollection",
+    "BTRowHeader",
+    "BTColumnHeader",
+]
+
+
+class BTTableData(BTBaseList):
+    def __init__(self, table, value=None):
+        if value is None:
+            value = []
+        self._table = table
+        self._value = value
+
+    def _get_canonical_key(self, key):
+        return self._table.rows._canonical_key(key)
+
+    def _get_ideal_length(self):
+        pass
 
 
 class BeautifulTable(object):
     """Utility Class to print data in tabular format to terminal.
-
     The instance attributes can be used to customize the look of the
     table. To disable a behaviour, just set its corresponding attribute
     to an empty string. For example, if Top border should not be drawn,
@@ -52,7 +75,7 @@ class BeautifulTable(object):
 
     Parameters
     ----------
-    max_width: int, optional
+    maxwidth: int, optional
         maximum width of the table in number of characters. this is ignored
         when manually setting the width of the columns. if this value is too
         low with respect to the number of columns and width of padding, the
@@ -64,8 +87,16 @@ class BeautifulTable(object):
     default_padding : int, optional
         Default width of the left and right padding for new columns(default 1).
 
+    precision : int, optional
+        All float values will have maximum number of digits after the decimal,
+        capped by this value(Default 3).
+
+    detect_numerics : bool, optional
+        Whether numeric strings should be automatically detected(Default True).
+
     Attributes
     ----------
+
     left_border_char : str
         Character used to draw the left border.
 
@@ -87,11 +118,6 @@ class BeautifulTable(object):
     column_separator_char : str
         Character used to draw the line seperating two columns.
 
-    intersection_char : str
-        Character used to draw intersection of a vertical and horizontal
-        line. Disabling it just draws the horizontal line char in it's place.
-        (DEPRECATED).
-
     intersect_top_left : str
         Left most character of the top border.
 
@@ -128,7 +154,7 @@ class BeautifulTable(object):
     intersect_bottom_right : str
         Right most character of the bottom border.
 
-    numeric_precision : int
+    precision : int
         All float values will have maximum number of digits after the decimal,
         capped by this value(Default 3).
 
@@ -144,30 +170,97 @@ class BeautifulTable(object):
         Whether numeric strings should be automatically detected(Default True).
     """
 
+    @deprecated_param("1.0.0", "1.2.0", "sign_mode", "sign")
+    @deprecated_param("1.0.0", "1.2.0", "numeric_precision", "precision")
+    @deprecated_param("1.0.0", "1.2.0", "max_width", "maxwidth")
+    @deprecated_param("1.0.0", "1.2.0", "serialno")
+    @deprecated_param("1.0.0", "1.2.0", "serialno_header")
     def __init__(
         self,
-        max_width=80,
+        maxwidth=80,
         default_alignment=enums.ALIGN_CENTER,
         default_padding=1,
+        precision=3,
+        serialno=False,
+        serialno_header="SN",
+        detect_numerics=True,
+        sign=enums.SM_MINUS,
+        **kwargs
     ):
 
+        kwargs.setdefault("max_width", None)
+        if kwargs["max_width"] is not None:
+            maxwidth = kwargs["max_width"]
+
+        kwargs.setdefault("numeric_precision", None)
+        if kwargs["numeric_precision"] is not None:
+            precision = kwargs["numeric_precision"]
+
+        kwargs.setdefault("sign_mode", None)
+        if kwargs["sign_mode"] is not None:
+            sign = kwargs["sign_mode"]
+
         self.set_style(enums.STYLE_DEFAULT)
 
-        self.numeric_precision = 3
-        self.serialno = False
-        self.serialno_header = "SN"
-        self.detect_numerics = True
+        self.precision = precision
+        self._serialno = serialno
+        self._serialno_header = serialno_header
+        self.detect_numerics = detect_numerics
+
+        self._sign = sign
+        self.maxwidth = maxwidth
+
+        self._ncol = 0
+        self._data = BTTableData(self)
+
+        self.rows = BTRowCollection(self)
+        self.columns = BTColumnCollection(
+            self, default_alignment, default_padding
+        )
+
+    def __copy__(self):
+        obj = type(self)()
+        obj.__dict__.update(
+            {k: copy.copy(v) for k, v in self.__dict__.items()}
+        )
+
+        obj.rows._table = obj
+        obj.rows.header._table = obj
+
+        obj.columns._table = obj
+        obj.columns.header._table = obj
+        obj.columns.alignment._table = obj
+        obj.columns.width._table = obj
+        obj.columns.padding_left._table = obj
+        obj.columns.padding_right._table = obj
 
-        self._column_count = 0
-        self._sign_mode = enums.SM_MINUS
-        self._width_exceed_policy = enums.WEP_WRAP
-        self._column_pad = " "
-        self.default_alignment = default_alignment
-        self.default_padding = default_padding
-        self.max_table_width = max_width
+        obj._data._table = obj
+        for row in obj._data:
+            row._table = obj
 
-        self._initialize_table(0)
-        self._table = []
+        return obj
+
+    def __deepcopy__(self, memo):
+        obj = type(self)()
+        obj.__dict__.update(
+            {k: copy.deepcopy(v, memo) for k, v in self.__dict__.items()}
+        )
+
+        obj.rows._table = obj
+        obj.rows.header._table = obj
+
+        obj.columns._table = obj
+        obj.columns.header._table = obj
+        obj.columns.alignment._table = obj
+        obj.columns.width._table = obj
+        obj.columns.padding_left._table = obj
+        obj.columns.padding_right._table = obj
+
+        obj._data._table = obj
+        for row in obj._data:
+            row._table = obj
+
+        return obj
 
     def __setattr__(self, name, value):
         attrs = (
@@ -200,47 +293,54 @@ class BeautifulTable(object):
             )
         super(BeautifulTable, self).__setattr__(name, value)
 
-    # ************************Properties Begin Here************************
-
-    @property
-    def column_count(self):
-        """Get the number of columns in the table(read only)"""
-        return self._column_count
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTRowCollection.__len__,
+        details="Use len(BeautifulTable.rows)' instead.",
+    )
+    def __len__(self):  # pragma: no cover
+        return len(self.rows)
+
+    @deprecated(
+        "1.0.0" "1.2.0",
+        BTRowCollection.__iter__,
+        details="Use iter(BeautifulTable.rows)' instead.",
+    )
+    def __iter__(self):  # pragma: no cover
+        return iter(self.rows)
+
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTColumnCollection.__contains__,
+        details="Use ''value' in BeautifulTable.{columns|rows}' instead.",
+    )
+    def __contains__(self, key):  # pragma: no cover
+        if isinstance(key, basestring):
+            return key in self.columns
+        elif isinstance(key, Iterable):
+            return key in self.rows
+        else:
+            raise TypeError(
+                ("'key' must be str or Iterable, " "not {}").format(
+                    type(key).__name__
+                )
+            )
 
-    @property
-    def intersection_char(self):  # pragma : no cover
-        """Character used to draw intersection of perpendicular lines.
+    def __repr__(self):
+        return repr(self._data)
 
-        Disabling it just draws the horizontal line char in it's place.
-        This attribute is deprecated. Use specific intersect_*_* attribute.
-        """
-        deprecation(
-            "'intersection_char' is deprecated, Use specific "
-            "`intersect_*_*` attribute instead"
-        )
-        return self.intersect_top_left
+    def __str__(self):
+        return self.get_string()
 
-    @intersection_char.setter
-    def intersection_char(self, value):  # pragma : no cover
-        deprecation(
-            "'intersection_char' is deprecated, Use specific "
-            "`intersect_*_*` attributes instead"
-        )
-        self.intersect_top_left = value
-        self.intersect_top_mid = value
-        self.intersect_top_right = value
-        self.intersect_header_left = value
-        self.intersect_header_mid = value
-        self.intersect_header_right = value
-        self.intersect_row_left = value
-        self.intersect_row_mid = value
-        self.intersect_row_right = value
-        self.intersect_bottom_left = value
-        self.intersect_bottom_mid = value
-        self.intersect_bottom_right = value
+    # ************************Properties Begin Here************************
+    @property
+    def shape(self):
+        return (len(self.rows), len(self.columns))
 
     @property
-    def sign_mode(self):
+    def sign(self):
         """Attribute to control how signs are displayed for numerical data.
 
         It can be one of the following:
@@ -257,387 +357,276 @@ class BeautifulTable(object):
                                    numbers and a minus sign for -ve numbers.
         ========================  =============================================
         """
-        return self._sign_mode
+        return self._sign
 
-    @sign_mode.setter
-    def sign_mode(self, value):
+    @sign.setter
+    def sign(self, value):
         if not isinstance(value, enums.SignMode):
             allowed = (
                 "{}.{}".format(type(self).__name__, i.name)
                 for i in enums.SignMode
             )
-            error_msg = "allowed values for sign_mode are: " + ", ".join(
-                allowed
-            )
+            error_msg = "allowed values for sign are: " + ", ".join(allowed)
             raise ValueError(error_msg)
-        self._sign_mode = value
+        self._sign = value
 
     @property
-    def width_exceed_policy(self):
-        """Attribute to control how exceeding column width should be handled.
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.header.fget)
+    def serialno(self):  # pragma: no cover
+        return self._serialno
 
-        It can be one of the following:
+    @serialno.setter
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.header.fget)
+    def serialno(self, value):  # pragma: no cover
+        self._serialno = value
 
-        ============================  =========================================
-         Option                        Meaning
-        ============================  =========================================
-         beautifulbable.WEP_WRAP       An item is wrapped so every line fits
-                                       within it's column width.
+    @property
+    @deprecated("1.0.0", "1.2.0")
+    def serialno_header(self):  # pragma: no cover
+        return self._serialno_header
+
+    @serialno_header.setter
+    @deprecated("1.0.0", "1.2.0")
+    def serialno_header(self, value):  # pragma: no cover
+        self._serialno_header = value
+
+    @property
+    @deprecated("1.0.0", "1.2.0", sign.fget)
+    def sign_mode(self):  # pragma: no cover
+        return self.sign
 
-         beautifultable.WEP_STRIP      An item is stripped to fit in it's
-                                       column.
+    @sign_mode.setter
+    @deprecated("1.0.0", "1.2.0", sign.fget)
+    def sign_mode(self, value):  # pragma: no cover
+        self.sign = value
 
-         beautifultable.WEP_ELLIPSIS   An item is stripped to fit in it's
-                                       column and appended with ...(Ellipsis).
-        ============================  =========================================
+    @property
+    def maxwidth(self):
+        """get/set the maximum width of the table.
+
+        The width of the table is guaranteed to not exceed this value. If it
+        is not possible to print a given table with the width provided, this
+        value will automatically adjust.
         """
-        return self._width_exceed_policy
+        offset = (len(self.columns) - 1) * termwidth(
+            self.column_separator_char
+        )
+        offset += termwidth(self.left_border_char)
+        offset += termwidth(self.right_border_char)
+        self._maxwidth = max(self._maxwidth, offset + len(self.columns))
+        return self._maxwidth
 
-    @width_exceed_policy.setter
-    def width_exceed_policy(self, value):
-        if not isinstance(value, enums.WidthExceedPolicy):
-            allowed = (
-                "{}.{}".format(type(self).__name__, i.name)
-                for i in enums.WidthExceedPolicy
-            )
-            error_msg = (
-                "allowed values for width_exceed_policy are: "
-                + ", ".join(allowed)
-            )
-            raise ValueError(error_msg)
-        self._width_exceed_policy = value
+    @maxwidth.setter
+    def maxwidth(self, value):
+        self._maxwidth = value
 
     @property
-    def default_alignment(self):
-        """Attribute to control the alignment of newly created columns.
+    @deprecated("1.0.0", "1.2.0", maxwidth.fget)
+    def max_table_width(self):  # pragma: no cover
+        return self.maxwidth
 
-        It can be one of the following:
+    @max_table_width.setter
+    @deprecated("1.0.0", "1.2.0", maxwidth.fget)
+    def max_table_width(self, value):  # pragma: no cover
+        self.maxwidth = value
 
-        ============================  =========================================
-         Option                        Meaning
-        ============================  =========================================
-         beautifultable.ALIGN_LEFT     New columns are left aligned.
+    @property
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTColumnCollection.__len__,
+        details="Use 'len(self.columns)' instead.",
+    )
+    def column_count(self):  # pragma: no cover
+        return len(self.columns)
 
-         beautifultable.ALIGN_CENTER   New columns are center aligned.
+    @property
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.width_exceed_policy.fget)
+    def width_exceed_policy(self):  # pragma: no cover
+        return self.columns.width_exceed_policy
 
-         beautifultable.ALIGN_RIGHT    New columns are right aligned.
-        ============================  =========================================
-        """
-        return self._default_alignment
+    @width_exceed_policy.setter
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.width_exceed_policy.fget)
+    def width_exceed_policy(self, value):  # pragma: no cover
+        self.columns.width_exceed_policy = value
+
+    @property
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.default_alignment.fget)
+    def default_alignment(self):  # pragma: no cover
+        return self.columns.default_alignment
 
     @default_alignment.setter
-    def default_alignment(self, value):
-        if not isinstance(value, enums.Alignment):
-            allowed = (
-                "{}.{}".format(type(self).__name__, i.name)
-                for i in enums.Alignment
-            )
-            error_msg = (
-                "allowed values for default_alignment are: "
-                + ", ".join(allowed)
-            )
-            raise ValueError(error_msg)
-        self._default_alignment = value
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.default_alignment.fget)
+    def default_alignment(self, value):  # pragma: no cover
+        self.columns.default_alignment = value
 
     @property
-    def default_padding(self):
-        """Initial value for Left and Right padding widths for new columns."""
-        return self._default_padding
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.default_padding.fget)
+    def default_padding(self):  # pragma: no cover
+        return self.columns.default_padding
 
     @default_padding.setter
-    def default_padding(self, value):
-        if not isinstance(value, int):
-            raise TypeError("padding must be an integer")
-        elif value < 0:
-            raise ValueError("padding must be equal to or greater than 0")
-        else:
-            self._default_padding = value
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.default_padding.fget)
+    def default_padding(self, value):  # pragma: no cover
+        self.columns.default_padding = value
 
     @property
-    def column_widths(self):
-        """get/set width for the columns of the table.
-
-        Width of the column specifies the max number of characters
-        a column can contain. Larger characters are handled according to
-        the value of `width_exceed_policy`.
-        """
-        return self._column_widths
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.width.fget)
+    def column_widths(self):  # pragma: no cover
+        return self.columns.width
 
     @column_widths.setter
-    def column_widths(self, value):
-        if isinstance(value, int):
-            value = [value] * self._column_count
-        width = self._validate_row(value)
-        self._column_widths = PositiveIntegerMetaData(self, width)
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.width.fget)
+    def column_widths(self, value):  # pragma: no cover
+        self.columns.width = value
 
     @property
-    def column_headers(self):
-        """get/set titles for the columns of the table.
-
-        It can be any iterable having all memebers an instance of `str`.
-        """
-        return self._column_headers
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.header.fget)
+    def column_headers(self):  # pragma: no cover
+        return self.columns.header
 
     @column_headers.setter
-    def column_headers(self, value):
-        header = self._validate_row(value)
-        for i in header:
-            if not isinstance(i, basestring):
-                raise TypeError(
-                    ("Headers should be of type 'str', " "not {}").format(
-                        type(i)
-                    )
-                )
-        self._column_headers = HeaderData(self, header)
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.header.fget)
+    def column_headers(self, value):  # pragma: no cover
+        self.columns.header = value
 
     @property
-    def column_alignments(self):
-        """get/set alignment of the columns of the table.
-
-        It can be any iterable containing only the following:
-
-        * beautifultable.ALIGN_LEFT
-        * beautifultable.ALIGN_CENTER
-        * beautifultable.ALIGN_RIGHT
-        """
-        return self._column_alignments
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.alignment.fget)
+    def column_alignments(self):  # pragma: no cover
+        return self.columns.alignment
 
     @column_alignments.setter
-    def column_alignments(self, value):
-        if isinstance(value, enums.Alignment):
-            value = [value] * self._column_count
-        alignment = self._validate_row(value)
-        self._column_alignments = AlignmentMetaData(self, alignment)
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.alignment.fget)
+    def column_alignments(self, value):  # pragma: no cover
+        self.columns.alignment = value
 
     @property
-    def left_padding_widths(self):
-        """get/set width for left padding of the columns of the table.
-
-        Left Width of the padding specifies the number of characters
-        on the left of a column reserved for padding. By Default It is 1.
-        """
-        return self._left_padding_widths
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.padding_left.fget)
+    def left_padding_widths(self):  # pragma: no cover
+        return self.columns.padding_left
 
     @left_padding_widths.setter
-    def left_padding_widths(self, value):
-        if isinstance(value, int):
-            value = [value] * self._column_count
-        pad_width = self._validate_row(value)
-        self._left_padding_widths = PositiveIntegerMetaData(self, pad_width)
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.padding_left.fget)
+    def left_padding_widths(self, value):  # pragma: no cover
+        self.columns.padding_left = value
 
     @property
-    def right_padding_widths(self):
-        """get/set width for right padding of the columns of the table.
-
-        Right Width of the padding specifies the number of characters
-        on the rigth of a column reserved for padding. By default It is 1.
-        """
-        return self._right_padding_widths
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.padding_right.fget)
+    def right_padding_widths(self):  # pragma: no cover
+        return self.columns.padding_right
 
     @right_padding_widths.setter
-    def right_padding_widths(self, value):
-        if isinstance(value, int):
-            value = [value] * self._column_count
-        pad_width = self._validate_row(value)
-        self._right_padding_widths = PositiveIntegerMetaData(self, pad_width)
-
-    @property
-    def max_table_width(self):
-        """get/set the maximum width of the table.
-
-        The width of the table is guaranteed to not exceed this value. If it
-        is not possible to print a given table with the width provided, this
-        value will automatically adjust.
-        """
-        offset = (self._column_count - 1) * termwidth(
-            self.column_separator_char
-        )
-        offset += termwidth(self.left_border_char)
-        offset += termwidth(self.right_border_char)
-        self._max_table_width = max(
-            self._max_table_width, offset + self._column_count
-        )
-        return self._max_table_width
-
-    @max_table_width.setter
-    def max_table_width(self, value):
-        self._max_table_width = value
-
-    # *************************Properties End Here*************************
-
-    def _initialize_table(self, column_count):
-        """Sets the column count of the table.
-
-        This method is called to set the number of columns for the first time.
-
-        Parameters
-        ----------
-        column_count : int
-            number of columns in the table
-        """
-        header = [""] * column_count
-        alignment = [self.default_alignment] * column_count
-        width = [0] * column_count
-        padding = [self.default_padding] * column_count
-
-        self._column_count = column_count
-        self._column_headers = HeaderData(self, header)
-        self._column_alignments = AlignmentMetaData(self, alignment)
-        self._column_widths = PositiveIntegerMetaData(self, width)
-        self._left_padding_widths = PositiveIntegerMetaData(self, padding)
-        self._right_padding_widths = PositiveIntegerMetaData(self, padding)
-
-    def _validate_row(self, value, init_table_if_required=True):
-        # TODO: Rename this method
-        # str is also an iterable but it is not a valid row, so
-        # an extra check is required for str
-        if not isinstance(value, Iterable) or isinstance(value, basestring):
-            raise TypeError("parameter must be an iterable")
-
-        row = list(value)
-        if init_table_if_required and self._column_count == 0:
-            self._initialize_table(len(row))
-
-        if len(row) != self._column_count:
-            raise ValueError(
-                ("'Expected iterable of length {}, " "got {}").format(
-                    self._column_count, len(row)
-                )
-            )
-        return row
-
-    def __getitem__(self, key):
-        """Get a row, or a column, or a new table by slicing.
-
-        Parameters
-        ----------
-        key : int, slice, str
-            If key is an `int`, returns a row.
-            If key is an `str`, returns iterator to a column with header `key`.
-            If key is a slice object, returns a new table sliced according to
-            rows.
-
-        Raises
-        ------
-        TypeError
-            If key is not of type int, slice or str.
-        IndexError
-            If `int` key is out of range.
-        KeyError
-            If `str` key is not found in headers.
-        """
-        if isinstance(key, slice):
-            new_table = copy.copy(self)
-            # Every child of BaseRow class needs to be reassigned so that
-            # They contain reference of the new table rather than the old
-            # This was a cause of a nasty bug once.
-            new_table.column_headers = self.column_headers
-            new_table.column_alignments = self.column_alignments
-            new_table.column_widths = self.column_widths
-            new_table.left_padding_widths = self.left_padding_widths
-            new_table.right_padding_widths = self.left_padding_widths
-            new_table._table = []
-            for row in self._table[key]:
-                new_table.append_row(row)
-            return new_table
-        if isinstance(key, int):
-            return self._table[key]
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.padding_right.fget)
+    def right_padding_widths(self, value):  # pragma: no cover
+        self.columns.padding_right = value
+
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTColumnCollection.__getitem__,
+        details="Use 'BeautifulTable.{columns|rows}[key]' instead.",
+    )
+    def __getitem__(self, key):  # pragma: no cover
         if isinstance(key, basestring):
-            return self.get_column(key)
-        raise TypeError(
-            (
-                "table indices must be integers, strings or " "slices, not {}"
-            ).format(type(key).__name__)
-        )
-
-    def __delitem__(self, key):
-        """Delete a row, or a column, or multiple rows by slicing.
-
-        Parameters
-        ----------
-        key : int, slice, str
-            If key is an `int`, deletes a row.
-            If key is a slice object, deletes multiple rows.
-            If key is an `str`, delete the first column with heading `key`
-
-        Raises
-        ------
-        TypeError
-            If key is not of type int, slice or str.
-        IndexError
-            If `int` key is out of range.
-        KeyError
-            If `str` key is not found in headers.
-        """
-        if isinstance(key, int) or isinstance(key, slice):
-            del self._table[key]
-        elif isinstance(key, basestring):
-            return self.pop_column(key)
-        else:
-            raise TypeError(
-                (
-                    "table indices must be integers, strings or "
-                    "slices, not {}"
-                ).format(type(key).__name__)
-            )
-
-    def __setitem__(self, key, value):
-        """Update a row, or a column, or multiple rows by slicing.
-
-        Parameters
-        ----------
-        key : int, slice, str
-            If key is an `int`, updates a row.
-            If key is an `str`, appends `column` to the list with header as
-            `key`.
-            If key is a slice object, updates multiple rows according to slice
-            rules.
-
-        Raises
-        ------
-        TypeError
-            If key is not of type int, slice or str.
-        IndexError
-            If `int` key is out of range.
-        """
-        if isinstance(key, (int, slice)):
-            self.update_row(key, value)
-        elif isinstance(key, basestring):
-            self.update_column(key, value)
+            return self.columns[key]
+        return self.rows[key]
+
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTColumnCollection.__setitem__,
+        details="Use 'BeautifulTable.{columns|rows}[key]' instead.",
+    )
+    def __setitem__(self, key, value):  # pragma: no cover
+        if isinstance(key, basestring):
+            self.columns[key] = value
         else:
-            raise TypeError(
-                (
-                    "table indices must be integers, strings or "
-                    "slices, not {}"
-                ).format(type(key).__name__)
-            )
-
-    def __len__(self):
-        return len(self._table)
-
-    def __contains__(self, key):
+            self.rows[key] = value
+
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTColumnCollection.__delitem__,
+        details="Use 'BeautifulTable.{columns|rows}[key]' instead.",
+    )
+    def __delitem__(self, key):  # pragma: no cover
         if isinstance(key, basestring):
-            return key in self._column_headers
-        elif isinstance(key, Iterable):
-            return key in self._table
+            del self.columns[key]
         else:
-            raise TypeError(
-                ("'key' must be str or Iterable, " "not {}").format(
-                    type(key).__name__
-                )
-            )
+            del self.rows[key]
 
-    def __iter__(self):
-        return iter(self._table)
-
-    def __next__(self):
-        return next(self._table)
-
-    def __repr__(self):
-        return repr(self._table)
+    # *************************Properties End Here*************************
 
-    def __str__(self):
-        return self.get_string()
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTColumnCollection.__getitem__,
+        details="Use 'BeautifulTable.columns[key]' instead.",
+    )
+    def get_column(self, key):  # pragma: no cover
+        return self.columns[key]
+
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTColumnHeader.__getitem__,
+        details="Use 'BeautifulTable.columns.header[key]' instead.",
+    )
+    def get_column_header(self, index):  # pragma: no cover
+        return self.columns.header[index]
+
+    @deprecated(
+        "1.0.0",
+        "1.2.0",
+        BTColumnHeader.__getitem__,
+        details="Use 'BeautifulTable.columns.header.index(header)' instead.",
+    )
+    def get_column_index(self, header):  # pragma: no cover
+        return self.columns.header.index(header)
+
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.filter)
+    def filter(self, key):  # pragma: no cover
+        return self.rows.filter(key)
+
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.sort)
+    def sort(self, key, reverse=False):  # pragma: no cover
+        self.rows.sort(key, reverse=reverse)
+
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.reverse)
+    def reverse(self, value):  # pragma: no cover
+        self.rows.reverse()
+
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.pop)
+    def pop_row(self, index=-1):  # pragma: no cover
+        return self.rows.pop(index)
+
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.insert)
+    def insert_row(self, index, row):  # pragma: no cover
+        return self.rows.insert(index, row)
+
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.append)
+    def append_row(self, value):  # pragma: no cover
+        self.rows.append(value)
+
+    @deprecated("1.0.0", "1.2.0", BTRowCollection.update)
+    def update_row(self, key, value):  # pragma: no cover
+        self.rows.update(key, value)
+
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.pop)
+    def pop_column(self, index=-1):  # pragma: no cover
+        return self.columns.pop(index)
+
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.insert)
+    def insert_column(self, index, header, column):  # pragma: no cover
+        self.columns.insert(index, column, header)
+
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.append)
+    def append_column(self, header, column):  # pragma: no cover
+        self.columns.append(column, header)
+
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.update)
+    def update_column(self, header, column):  # pragma: no cover
+        self.columns.update(header, column)
 
     def set_style(self, style):
         """Set the style of the table from a predefined set of styles.
@@ -689,50 +678,49 @@ class BeautifulTable(object):
         self.intersect_bottom_mid = style_template.intersect_bottom_mid
         self.intersect_bottom_right = style_template.intersect_bottom_right
 
-    def _calculate_column_widths(self):
+    def _compute_width(self):
         """Calculate width of column automatically based on data."""
-        table_width = self.get_table_width()
-        lpw, rpw = self._left_padding_widths, self._right_padding_widths
-        pad_widths = [(lpw[i] + rpw[i]) for i in range(self._column_count)]
-        max_widths = [0 for index in range(self._column_count)]
-        offset = table_width - sum(self._column_widths) + sum(pad_widths)
-        self._max_table_width = max(
-            self._max_table_width, offset + self._column_count
-        )
-
-        for index, header in enumerate(self.column_headers):
+        table_width = self.width
+        lpw, rpw = self.columns.padding_left, self.columns.padding_right
+        pad_widths = [(lpw[i] + rpw[i]) for i in range(len(self.columns))]
+        maxwidths = [0 for index in range(len(self.columns))]
+        offset = table_width - sum(self.columns.width) + sum(pad_widths)
+        self._maxwidth = max(self._maxwidth, offset + len(self.columns))
+
+        for index, header in enumerate(self.columns.header):
             max_length = 0
-            for i in to_unicode(self._column_headers[index]).split("\n"):
-                output_str = get_output_str(
-                    i,
-                    self.detect_numerics,
-                    self.numeric_precision,
-                    self.sign_mode.value,
+            for i in pre_process(
+                header, self.detect_numerics, self.precision, self.sign.value
+            ).split("\n"):
+                output_str = pre_process(
+                    i, self.detect_numerics, self.precision, self.sign.value,
                 )
                 max_length = max(max_length, termwidth(output_str))
-            max_widths[index] += max_length
+            maxwidths[index] += max_length
 
-        for index, column in enumerate(zip(*self._table)):
-            max_length = max_widths[index]
+        for index, column in enumerate(zip(*self._data)):
+            max_length = maxwidths[index]
             for i in column:
-                for j in to_unicode(i).split("\n"):
-                    output_str = get_output_str(
+                for j in pre_process(
+                    i, self.detect_numerics, self.precision, self.sign.value
+                ).split("\n"):
+                    output_str = pre_process(
                         j,
                         self.detect_numerics,
-                        self.numeric_precision,
-                        self.sign_mode.value,
+                        self.precision,
+                        self.sign.value,
                     )
                     max_length = max(max_length, termwidth(output_str))
-            max_widths[index] = max_length
+            maxwidths[index] = max_length
 
-        sum_ = sum(max_widths)
-        desired_sum = self._max_table_width - offset
+        sum_ = sum(maxwidths)
+        desired_sum = self._maxwidth - offset
 
         # Set flag for columns who are within their fair share
         temp_sum = 0
-        flag = [0] * len(max_widths)
-        for i, width in enumerate(max_widths):
-            if width <= int(desired_sum / self._column_count):
+        flag = [0] * len(maxwidths)
+        for i, width in enumerate(maxwidths):
+            if width <= int(desired_sum / len(self.columns)):
                 temp_sum += width
                 flag[i] = 1
             else:
@@ -745,71 +733,37 @@ class BeautifulTable(object):
 
         # Columns which exceed their fair share should be shrinked based on
         # how much space is left for the table
-        for i, width in enumerate(max_widths):
-            self.column_widths[i] = width
+        for i, width in enumerate(maxwidths):
+            self.columns.width[i] = width
             if not flag[i]:
                 new_width = 1 + int((width - 1) * avail_space / actual_space)
                 if new_width < width:
-                    self.column_widths[i] = new_width
+                    self.columns.width[i] = new_width
                     shrinked_columns[new_width] = i
 
         # Divide any remaining space among shrinked columns
         if shrinked_columns:
-            extra = self._max_table_width - offset - sum(self.column_widths)
+            extra = self._maxwidth - offset - sum(self.columns.width)
             actual_space = sum(shrinked_columns)
 
             if extra > 0:
                 for i, width in enumerate(sorted(shrinked_columns)):
                     index = shrinked_columns[width]
                     extra_width = int(width * extra / actual_space)
-                    self.column_widths[i] += extra_width
+                    self.columns.width[i] += extra_width
                     if i == (len(shrinked_columns) - 1):
                         extra = (
-                            self._max_table_width
-                            - offset
-                            - sum(self.column_widths)
+                            self._maxwidth - offset - sum(self.columns.width)
                         )
-                        self.column_widths[index] += extra
+                        self.columns.width[index] += extra
 
-        for i in range(self.column_count):
-            self.column_widths[i] += pad_widths[i]
+        for i in range(len(self.columns)):
+            self.columns.width[i] += pad_widths[i]
 
-    def auto_calculate_width(self):  # pragma : no cover
-        deprecation("'auto_calculate_width()' is deprecated")
-        self._calculate_column_widths()
-
-    def set_padding_widths(self, pad_width):
-        """Set width for left and rigth padding of the columns of the table.
-
-        Parameters
-        ----------
-        pad_width : array_like
-            pad widths for the columns.
-        """
-        self.left_padding_widths = pad_width
-        self.right_padding_widths = pad_width
-
-    def sort(self, key, reverse=False):
-        """Stable sort of the table *IN-PLACE* with respect to a column.
-
-        Parameters
-        ----------
-        key: int, str
-            index or header of the column. Normal list rules apply.
-        reverse : bool
-            If `True` then table is sorted as if each comparison was reversed.
-        """
-        if isinstance(key, int):
-            key = operator.itemgetter(key)
-        elif isinstance(key, basestring):
-            key = operator.itemgetter(self.get_column_index(key))
-        elif callable(key):
-            pass
-        else:
-            raise TypeError(
-                "'key' must either be 'int' or 'str' or a 'callable'"
-            )
-        self._table.sort(key=key, reverse=reverse)
+    @deprecated("1.0.0", "1.2.0", BTColumnCollection.padding.fget)
+    def set_padding_widths(self, pad_width):  # pragma: no cover
+        self.columns.padding_left = pad_width
+        self.columns.padding_right = pad_width
 
     def copy(self):
         """Return a shallow copy of the table.
@@ -819,320 +773,10 @@ class BeautifulTable(object):
         BeautifulTable:
             shallow copy of the BeautifulTable instance.
         """
-        return self[:]
-
-    def filter(self, key):
-        """Return a copy of the table with only those rows which satisfy a
-        certain condition.
-
-        Returns
-        -------
-        BeautifulTable:
-            Filtered copy of the BeautifulTable instance.
-        """
-        new_table = self.copy()
-        new_table.clear()
-        for row in filter(key, self):
-            new_table.append_row(row)
-        return new_table
-
-    def get_column_header(self, index):
-        """Get header of a column from it's index.
-
-        Parameters
-        ----------
-        index: int
-            Normal list rules apply.
-        """
-        return self._column_headers[index]
-
-    def get_column_index(self, header):
-        """Get index of a column from it's header.
-
-        Parameters
-        ----------
-        header: str
-            header of the column.
-
-        Raises
-        ------
-        ValueError:
-            If no column could be found corresponding to `header`.
-        """
-        try:
-            index = self._column_headers.index(header)
-            return index
-        except ValueError:
-            raise_suppressed(
-                KeyError(
-                    ("'{}' is not a header for any " "column").format(header)
-                )
-            )
-
-    def get_column(self, key):
-        """Return an iterator to a column.
-
-        Parameters
-        ----------
-        key : int, str
-            index of the column, or the header of the column.
-            If index is specified, then normal list rules apply.
-
-        Raises
-        ------
-        TypeError:
-            If key is not of type `int`, or `str`.
-
-        Returns
-        -------
-        iter:
-            Iterator to the specified column.
-        """
-        if isinstance(key, int):
-            index = key
-        elif isinstance(key, basestring):
-            index = self.get_column_index(key)
-        else:
-            raise TypeError(
-                ("key must be an int or str, " "not {}").format(
-                    type(key).__name__
-                )
-            )
-        return iter(map(operator.itemgetter(index), self._table))
-
-    def reverse(self):
-        """Reverse the table row-wise *IN PLACE*."""
-        self._table.reverse()
-
-    def pop_row(self, index=-1):
-        """Remove and return row at index (default last).
-
-        Parameters
-        ----------
-        index : int
-            index of the row. Normal list rules apply.
-        """
-        row = self._table.pop(index)
-        return row
-
-    def pop_column(self, index=-1):
-        """Remove and return row at index (default last).
-
-        Parameters
-        ----------
-        index : int, str
-            index of the column, or the header of the column.
-            If index is specified, then normal list rules apply.
-
-        Raises
-        ------
-        TypeError:
-            If index is not an instance of `int`, or `str`.
-
-        IndexError:
-            If Table is empty.
-        """
-        if isinstance(index, int):
-            pass
-        elif isinstance(index, basestring):
-            index = self.get_column_index(index)
-        else:
-            raise TypeError(
-                (
-                    "column index must be an integer or a string, " "not {}"
-                ).format(type(index).__name__)
-            )
-        if self._column_count == 0:
-            raise IndexError("pop from empty table")
-        if self._column_count == 1:
-            # This is the last column. So we should clear the table to avoid
-            # empty rows
-            self.clear(clear_metadata=True)
-        else:
-            # Not the last column. safe to pop from row
-            self._column_count -= 1
-            self._column_alignments._pop(index)
-            self._column_widths._pop(index)
-            self._left_padding_widths._pop(index)
-            self._right_padding_widths._pop(index)
-            self._column_headers._pop(index)
-            for row in self._table:
-                row._pop(index)
-
-    def insert_row(self, index, row):
-        """Insert a row before index in the table.
-
-        Parameters
-        ----------
-        index : int
-            List index rules apply
-
-        row : iterable
-            Any iterable of appropriate length.
-
-        Raises
-        ------
-        TypeError:
-            If `row` is not an iterable.
-
-        ValueError:
-            If size of `row` is inconsistent with the current number
-            of columns.
-        """
-        row = self._validate_row(row)
-        row_obj = RowData(self, row)
-        self._table.insert(index, row_obj)
-
-    def append_row(self, row):
-        """Append a row to end of the table.
-
-        Parameters
-        ----------
-        row : iterable
-            Any iterable of appropriate length.
-
-        """
-        self.insert_row(len(self._table), row)
-
-    def update_row(self, key, value):
-        """Update a column named `header` in the table.
-
-        If length of column is smaller than number of rows, lets say
-        `k`, only the first `k` values in the column is updated.
-
-        Parameters
-        ----------
-        key : int or slice
-            index of the row, or a slice object.
-
-        value : iterable
-            If an index is specified, `value` should be an iterable
-            of appropriate length. Instead if a slice object is
-            passed as key, value should be an iterable of rows.
-
-        Raises
-        ------
-        IndexError:
-            If index specified is out of range.
-
-        TypeError:
-            If `value` is of incorrect type.
-
-        ValueError:
-            If length of row does not matches number of columns.
-        """
-        if isinstance(key, int):
-            row = self._validate_row(value, init_table_if_required=False)
-            row_obj = RowData(self, row)
-            self._table[key] = row_obj
-        elif isinstance(key, slice):
-            row_obj_list = []
-            for row in value:
-                row_ = self._validate_row(row, init_table_if_required=True)
-                row_obj_list.append(RowData(self, row_))
-            self._table[key] = row_obj_list
-        else:
-            raise TypeError("key must be an integer or a slice object")
-
-    def update_column(self, header, column):
-        """Update a column named `header` in the table.
+        return copy.copy(self)
 
-        If length of column is smaller than number of rows, lets say
-        `k`, only the first `k` values in the column is updated.
-
-        Parameters
-        ----------
-        header : str
-            Header of the column
-
-        column : iterable
-            Any iterable of appropriate length.
-
-        Raises
-        ------
-        TypeError:
-            If length of `column` is shorter than number of rows.
-
-        ValueError:
-            If no column exists with title `header`.
-        """
-        index = self.get_column_index(header)
-        if not isinstance(header, basestring):
-            raise TypeError("header must be of type str")
-        for row, new_item in zip(self._table, column):
-            row[index] = new_item
-
-    def insert_column(self, index, header, column):
-        """Insert a column before `index` in the table.
-
-        If length of column is bigger than number of rows, lets say
-        `k`, only the first `k` values of `column` is considered.
-        If column is shorter than 'k', ValueError is raised.
-
-        Note that Table remains in consistent state even if column
-        is too short. Any changes made by this method is rolled back
-        before raising the exception.
-
-        Parameters
-        ----------
-        index : int
-            List index rules apply.
-
-        header : str
-            Title of the column.
-
-        column : iterable
-            Any iterable of appropriate length.
-
-        Raises
-        ------
-        TypeError:
-            If `header` is not of type `str`.
-
-        ValueError:
-            If length of `column` is shorter than number of rows.
-        """
-        if self._column_count == 0:
-            self.column_headers = HeaderData(self, [header])
-            self._table = [RowData(self, [i]) for i in column]
-        else:
-            if not isinstance(header, basestring):
-                raise TypeError("header must be of type str")
-            column_length = 0
-            for row, new_item in zip(self._table, column):
-                row._insert(index, new_item)
-                column_length += 1
-            if column_length == len(self._table):
-                self._column_count += 1
-                self._column_headers._insert(index, header)
-                self._column_alignments._insert(index, self.default_alignment)
-                self._column_widths._insert(index, 0)
-                self._left_padding_widths._insert(index, self.default_padding)
-                self._right_padding_widths._insert(index, self.default_padding)
-            else:
-                # Roll back changes so that table remains in consistent state
-                for j in range(column_length, -1, -1):
-                    self._table[j]._pop(index)
-                raise ValueError(
-                    (
-                        "length of 'column' should be atleast {}, " "got {}"
-                    ).format(len(self._table), column_length)
-                )
-
-    def append_column(self, header, column):
-        """Append a column to end of the table.
-
-        Parameters
-        ----------
-        header : str
-            Title of the column
-
-        column : iterable
-            Any iterable of appropriate length.
-        """
-        self.insert_column(self._column_count, header, column)
-
-    def clear(self, clear_metadata=False):
+    @deprecated_param("1.0.0", "1.2.0", "clear_metadata", "reset_columns")
+    def clear(self, reset_columns=False, **kwargs):  # pragma: no cover
         """Clear the contents of the table.
 
         Clear all rows of the table, and if specified clears all column
@@ -1140,22 +784,24 @@ class BeautifulTable(object):
 
         Parameters
         ----------
-        clear_metadata : bool, optional
+        reset_columns : bool, optional
             If it is true(default False), all metadata of columns such as their
             alignment, padding, width, etc. are also cleared and number of
             columns is set to 0.
         """
-        # Cannot use clear method to support Python 2.7
-        del self._table[:]
-        if clear_metadata:
-            self._initialize_table(0)
+        kwargs.setdefault("clear_metadata", None)
+        if kwargs["clear_metadata"]:
+            reset_columns = kwargs["clear_metadata"]
+        self.rows.clear()
+        if reset_columns:
+            self.columns.clear()
 
     def _get_horizontal_line(
-        self, char, intersect_left, intersect_mid, intersect_right
+        self, char, intersect_left, intersect_mid, intersect_right, mask=None
     ):
         """Get a horizontal line for the table.
 
-        Internal method used to actually get all horizontal lines in the table.
+        Internal method used to draw all horizontal lines in the table.
         Column width should be set prior to calling this method. This method
         detects intersection and handles it according to the values of
         `intersect_*_*` attributes.
@@ -1168,9 +814,12 @@ class BeautifulTable(object):
         Returns
         -------
         str
-            String which will be printed as the Top border of the table.
+            String which will be printed as a line in the table.
         """
-        width = self.get_table_width()
+        width = self.width
+
+        if mask is None:
+            mask = [True] * len(self.columns)
 
         try:
             line = list(char * (int(width / termwidth(char)) + 1))[:width]
@@ -1192,7 +841,7 @@ class BeautifulTable(object):
                         termwidth(intersect_left),
                     )
                     for i in range(length):
-                        line[i] = intersect_left[i]
+                        line[i] = intersect_left[i] if mask[0] else " "
             visible_junc = not intersect_right.isspace()
             # If right border is enabled and it is visible
             if termwidth(self.right_border_char) > 0:
@@ -1202,110 +851,77 @@ class BeautifulTable(object):
                         termwidth(intersect_right),
                     )
                     for i in range(length):
-                        line[-i - 1] = intersect_right[-i - 1]
+                        line[-i - 1] = (
+                            intersect_right[-i - 1] if mask[-1] else " "
+                        )
             visible_junc = not intersect_mid.isspace()
             # If column separator is enabled and it is visible
             if termwidth(self.column_separator_char):
                 if not (self.column_separator_char.isspace() and visible_junc):
                     index = termwidth(self.left_border_char)
-                    for i in range(self._column_count - 1):
-                        index += self._column_widths[i]
+                    for i in range(len(self.columns) - 1):
+                        if not mask[i]:
+                            for j in range(self.columns.width[i]):
+                                line[index + j] = " "
+                        index += self.columns.width[i]
                         length = min(
                             termwidth(self.column_separator_char),
                             termwidth(intersect_mid),
                         )
                         for j in range(length):
-                            line[index + j] = intersect_mid[j]
+                            # TODO: we should also hide junctions based on mask
+                            line[index + j] = (
+                                intersect_mid[j]
+                                if (mask[i] or mask[i + 1])
+                                else " "
+                            )
                         index += termwidth(self.column_separator_char)
 
         return "".join(line)
 
-    def _get_top_border(self):
+    def _get_top_border(self, *args, **kwargs):
         return self._get_horizontal_line(
             self.top_border_char,
             self.intersect_top_left,
             self.intersect_top_mid,
             self.intersect_top_right,
+            *args,
+            **kwargs
         )
 
-    def get_top_border(self):  # pragma : no cover
-        """Get the Top border of table.
-
-        Column width should be set prior to calling this method.
-
-        Returns
-        -------
-        str
-            String which will be printed as the Top border of the table.
-        """
-        deprecation("'get_top_border()' is deprecated")
-        return self._get_top_border()
-
-    def _get_header_separator(self):
+    def _get_header_separator(self, *args, **kwargs):
         return self._get_horizontal_line(
             self.header_separator_char,
             self.intersect_header_left,
             self.intersect_header_mid,
             self.intersect_header_right,
+            *args,
+            **kwargs
         )
 
-    def get_header_separator(self):  # pragma : no cover
-        """Get the Header separator of table.
-
-        Column width should be set prior to calling this method.
-
-        Returns
-        -------
-        str
-            String which will be printed as Header separator of the table.
-        """
-        deprecation("'get_header_separator()' is deprecated")
-        return self._get_header_separator()
-
-    def _get_row_separator(self):
+    def _get_row_separator(self, *args, **kwargs):
         return self._get_horizontal_line(
             self.row_separator_char,
             self.intersect_row_left,
             self.intersect_row_mid,
             self.intersect_row_right,
+            *args,
+            **kwargs
         )
 
-    def get_row_separator(self):  # pragma : no cover
-        """Get the Row separator of table.
-
-        Column width should be set prior to calling this method.
-
-        Returns
-        -------
-        str
-            String which will be printed as Row separator of the table.
-        """
-        deprecation("'get_row_separator()' is deprecated")
-        return self._get_row_separator()
-
-    def _get_bottom_border(self):
+    def _get_bottom_border(self, *args, **kwargs):
         return self._get_horizontal_line(
             self.bottom_border_char,
             self.intersect_bottom_left,
             self.intersect_bottom_mid,
             self.intersect_bottom_right,
+            *args,
+            **kwargs
         )
 
-    def get_bottom_border(self):  # pragma : no cover
-        """Get the Bottom border of table.
-
-        Column width should be set prior to calling this method.
-
-        Returns
-        -------
-        str
-            String which will be printed as Bottom border of the table.
-        """
-        deprecation("'get_bottom_border()' is deprecated")
-        return self._get_bottom_border()
-
-    def get_table_width(self):
-        """Get the width of the table as number of characters.
+    @property
+    def width(self):
+        """Get the actual width of the table as number of characters.
 
         Column width should be set prior to calling this method.
 
@@ -1314,80 +930,128 @@ class BeautifulTable(object):
         int
             Width of the table as number of characters.
         """
-        if self.column_count == 0:
+        if len(self.columns) == 0:
             return 0
-        width = sum(self._column_widths)
-        width += (self._column_count - 1) * termwidth(
+        width = sum(self.columns.width)
+        width += (len(self.columns) - 1) * termwidth(
             self.column_separator_char
         )
         width += termwidth(self.left_border_char)
         width += termwidth(self.right_border_char)
         return width
 
-    def _get_string(self, rows, append=False, recalculate_width=False):
-        # Drawing the top border
-        if self.serialno:
-            if self.column_count > 0:
-                self.insert_column(
-                    0, self.serialno_header, range(1, len(self) + 1)
+    @deprecated("1.0.0", "1.2.0", width.fget)
+    def get_table_width(self):  # pragma: no cover
+        return self.width
+
+    def _get_string(self, rows, append=False, recalculate_width=True):
+        row_header_visible = bool(
+            "".join(
+                x if x is not None else "" for x in self.rows.header
+            ).strip()
+        )
+        column_header_visible = bool(
+            "".join(
+                x if x is not None else "" for x in self.columns.header
+            ).strip()
+        )
+
+        # Preparing table for printing serialno, row headers and column headers
+        if len(self.columns) > 0:
+            if self._serialno:
+                self.columns.insert(
+                    0, range(1, len(self.rows) + 1), self._serialno_header
                 )
 
-        if recalculate_width or sum(self._column_widths) == 0:
-            self._calculate_column_widths()
+            if row_header_visible:
+                self.columns.insert(0, self.rows.header)
+
+        if len(self.rows) > 0:
+            if column_header_visible:
+                self.rows.insert(0, self.columns.header)
+
+        if (self.columns._auto_width and recalculate_width) or sum(
+            self.columns.width
+        ) == 0:
+            self._compute_width()
+
+        if len(self.columns) > 0:
+            if self._serialno:
+                index = 1 if row_header_visible else 0
+                if self.columns.width[index] == 0:
+                    self.columns.width[index] = (
+                        max(len(i) for i in self.rows.header)
+                        + 2 * self.columns.default_padding
+                    )
+
+            if row_header_visible:
+                if self.columns.width[0] == 0:
+                    self.columns.width[0] = (
+                        max(4, len(self._serialno_header))
+                        + 2 * self.columns.default_padding
+                    )
 
-        if self.serialno:
-            if self.column_count > 0 and self.column_widths[0] == 0:
-                self.column_widths[0] = (
-                    max(4, len(self.serialno_header))
-                    + 2 * self.default_padding
+        try:
+            # Rendering the top border
+            if self.top_border_char:
+                yield self._get_top_border()
+
+            # Print column headers if not empty or only spaces
+            row_iterator = iter(self.rows)
+            if column_header_visible:
+                yield next(row_iterator)._get_string(
+                    align=self.columns.header.alignment
                 )
+                if self.header_separator_char:
+                    yield self._get_header_separator()
+
+            # Printing rows
+            first_row_encountered = False
+            for i, row in enumerate(row_iterator):
+                if first_row_encountered and self.row_separator_char:
+                    yield self._get_row_separator()
+                first_row_encountered = True
+                content = to_unicode(row)
+                yield content
+
+            # Printing additional rows
+            prev_length = len(self.rows)
+            for i, row in enumerate(rows, start=1):
+                if first_row_encountered and self.row_separator_char:
+                    yield self._get_row_separator()
+                first_row_encountered = True
+                if self._serialno:
+                    row.insert(0, prev_length + i)
+                self.rows.append(row)
+                content = to_unicode(self.rows[-1])
+                if not append:
+                    self.rows.pop()
+                yield content
+
+            # Rendering the bottom border
+            if self.bottom_border_char:
+                yield self._get_bottom_border()
+        except Exception:
+            raise
+        finally:
+            # Cleanup
+            if len(self.rows) > 0:
+                if column_header_visible:
+                    self.rows.pop(0)
+
+            if len(self.columns) > 0:
+                if row_header_visible:
+                    self.columns.pop(0)
 
-        if self.top_border_char:
-            yield self._get_top_border()
-
-        # Print headers if not empty or only spaces
-        if "".join(self._column_headers).strip():
-            headers = to_unicode(self._column_headers)
-            yield headers
-
-            if self.header_separator_char:
-                yield self._get_header_separator()
-
-        # Printing rows
-        first_row_encountered = False
-        for row in self._table:
-            if first_row_encountered and self.row_separator_char:
-                yield self._get_row_separator()
-            first_row_encountered = True
-            content = to_unicode(row)
-            yield content
-
-        prev_length = len(self)
-        for i, row in enumerate(rows, start=1):
-            if first_row_encountered and self.row_separator_char:
-                yield self._get_row_separator()
-            first_row_encountered = True
-            if self.serialno:
-                row.insert(0, prev_length + i)
-            self.append_row(row)
-            content = to_unicode(self._table[-1])
-            if not append:
-                self.pop_row()
-            yield content
-
-        # Drawing the bottom border
-        if self.bottom_border_char:
-            yield self._get_bottom_border()
-
-        if self.serialno and self.column_count > 0:
-            self.pop_column(0)
+                if self._serialno:
+                    self.columns.pop(0)
 
     def stream(self, rows, append=False):
         """Get a generator for the table.
 
         This should be used in cases where data takes time to retrieve and
-        it is required be displayed as soon as possible. Any existing rows
-        in the table shall also be returned. It is required that atleast one
+        it is required to be displayed as soon as possible. Any existing rows
+        in the table shall also be returned. It is essential that atleast one
         of title, width or existing rows set prior to calling this method.
 
         Parameters
@@ -1408,16 +1072,8 @@ class BeautifulTable(object):
         ):
             yield line
 
-    def get_string(self, recalculate_width=True):
-        """Get the table as a String.
-
-        Parameters
-        ----------
-        recalculate_width : bool, optional
-            If width for each column should be recalculated(default True).
-            Note that width is always calculated if it wasn't set
-            explicitly when this method is called for the first time ,
-            regardless of the value of `recalculate_width`.
+    def get_string(self):
+        """Get the table as a string.
 
         Returns
         -------
@@ -1425,27 +1081,22 @@ class BeautifulTable(object):
             Table as a string.
         """
 
-        if len(self._table) == 0:
+        if len(self.rows) == 0:
             return ""
 
         string_ = []
-        for line in self._get_string(
-            [], append=False, recalculate_width=recalculate_width
-        ):
+        for line in self._get_string([], append=False):
             string_.append(line)
 
         return "\n".join(string_)
 
-    def to_csv(self, file_name, delimiter=","):
+    def to_csv(self, file_name, *args, **kwargs):
         """Export table to CSV format.
 
         Parameters
         ----------
         file_name : str
-            Path to CSV file which BeautifulTable will write to.
-
-        delimiter : str, optional
-            Delimiter used as value separator. Defaults to comma (',').
+            Path to CSV file.
         """
 
         if not isinstance(file_name, str):
@@ -1455,27 +1106,25 @@ class BeautifulTable(object):
                 )
             )
 
-        try:
-            with open(file_name, mode="wt", newline="") as csv_file:
-                csv_writer = csv.writer(
-                    csv_file, delimiter=delimiter, quoting=csv.QUOTE_MINIMAL
-                )
-                csv_writer.writerow(self.column_headers)  # write header
-                csv_writer.writerows(self._table)  # write table
-        except OSError:
-            raise
-
-    def from_csv(self, file_name, delimiter=",", header_exists=True):
+        with open(file_name, mode="wt", newline="") as csv_file:
+            csv_writer = csv.writer(csv_file, *args, **kwargs)
+            if bool(
+                "".join(
+                    x if x is not None else "" for x in self.columns.header
+                ).strip()
+            ):
+                csv_writer.writerow(self.columns.header)
+            csv_writer.writerows(self.rows)
+
+    def from_csv(self, file_name, header=True, **kwargs):
         """Create table from CSV file.
 
         Parameters
         ----------
         file_name : str
-            Path to CSV file which `BeautifulTable` will read from.
-        delimiter : str, optional
-            Delimiter used as value separator. Defaults to comma (`,`).
-        header_exists : bool, optional
-            First row in CSV file should be set as table header.
+            Path to CSV file.
+        header : bool, optional
+            Whether First row in CSV file should be parsed as table header.
 
         Raises
         ------
@@ -1492,16 +1141,11 @@ class BeautifulTable(object):
                 )
             )
 
-        try:
-            with open(file_name, mode="rt", newline="") as csv_file:
-                csv_file = csv.reader(csv_file, delimiter=delimiter)
-
-                if header_exists:
-                    self.column_headers = next(csv_file)
+        with open(file_name, mode="rt", newline="") as csv_file:
+            csv_reader = csv.reader(csv_file, **kwargs)
 
-                for row in csv_file:
-                    self.append_row(row)
-
-                return self
-        except FileNotFoundError:
-            raise
+            if header:
+                self.columns.header = next(csv_reader)
+            for row in csv_reader:
+                self.rows.append(row)
+            return self
diff --git a/beautifultable/exceptions.py b/beautifultable/exceptions.py
deleted file mode 100644
index 4f3f876..0000000
--- a/beautifultable/exceptions.py
+++ /dev/null
@@ -1,2 +0,0 @@
-class BeautifulTableDeprecationWarning(DeprecationWarning):
-    pass
diff --git a/beautifultable/helpers.py b/beautifultable/helpers.py
new file mode 100644
index 0000000..5cf079e
--- /dev/null
+++ b/beautifultable/helpers.py
@@ -0,0 +1,1178 @@
+import copy
+import weakref
+import operator
+
+from . import enums
+from .base import BTBaseRow, BTBaseColumn
+from .utils import pre_process, termwidth, textwrap
+from .compat import basestring, Iterable, to_unicode, zip_longest
+from .meta import AlignmentMetaData, NonNegativeIntegerMetaData
+
+
+class BTRowHeader(BTBaseColumn):
+    def __init__(self, table, value):
+        for i in value:
+            self._validate_item(i)
+        super(BTRowHeader, self).__init__(table, value)
+
+    def __setitem__(self, key, value):
+        self._validate_item(value)
+        super(BTRowHeader, self).__setitem__(key, value)
+
+    def _validate_item(self, value):
+        if not (isinstance(value, basestring) or value is None):
+            raise TypeError(
+                ("header must be of type 'str', " "got {}").format(
+                    type(value).__name__
+                )
+            )
+
+
+class BTColumnHeader(BTBaseRow):
+    def __init__(self, table, value):
+        for i in value:
+            self._validate_item(i)
+        super(BTColumnHeader, self).__init__(table, value)
+        self.alignment = None
+
+    @property
+    def alignment(self):
+        """get/set alignment of the column header of the table.
+
+        It can be any iterable containing only the following:
+
+        * beautifultable.ALIGN_LEFT
+        * beautifultable.ALIGN_CENTER
+        * beautifultable.ALIGN_RIGHT
+        """
+        return self._alignment
+
+    @alignment.setter
+    def alignment(self, value):
+        if value is None:
+            self._alignment = None
+            return
+        if isinstance(value, enums.Alignment):
+            value = [value] * len(self)
+        self._alignment = AlignmentMetaData(self._table, value)
+
+    def __setitem__(self, key, value):
+        self._validate_item(value)
+        super(BTColumnHeader, self).__setitem__(key, value)
+
+    def _validate_item(self, value):
+        if not (isinstance(value, basestring) or value is None):
+            raise TypeError(
+                ("header must be of type 'str', " "got {}").format(
+                    type(value).__name__
+                )
+            )
+
+
+class BTRowData(BTBaseRow):
+    def _get_padding(self):
+        return (
+            self._table.columns.padding_left,
+            self._table.columns.padding_right,
+        )
+
+    def _clamp_row(self, row):
+        """Process a row so that it is clamped by column_width.
+
+        Parameters
+        ----------
+        row : array_like
+             A single row.
+
+        Returns
+        -------
+        list of list:
+            List representation of the `row` after it has been processed
+            according to width exceed policy.
+        """
+        table = self._table
+        lpw, rpw = self._get_padding()
+        wep = table.columns.width_exceed_policy
+
+        result = []
+
+        if (
+            wep is enums.WidthExceedPolicy.WEP_STRIP
+            or wep is enums.WidthExceedPolicy.WEP_ELLIPSIS
+        ):
+
+            # Let's strip the row
+            delimiter = (
+                "" if wep is enums.WidthExceedPolicy.WEP_STRIP else "..."
+            )
+            row_item_list = []
+            for index, row_item in enumerate(row):
+                left_pad = table.columns._pad_character * lpw[index]
+                right_pad = table.columns._pad_character * rpw[index]
+                clmp_str = (
+                    left_pad
+                    + self._clamp_string(row_item, index, delimiter)
+                    + right_pad
+                )
+                row_item_list.append(clmp_str)
+            result.append(row_item_list)
+        elif wep is enums.WidthExceedPolicy.WEP_WRAP:
+
+            # Let's wrap the row
+            string_partition = []
+
+            for index, row_item in enumerate(row):
+                width = table.columns.width[index] - lpw[index] - rpw[index]
+                string_partition.append(textwrap(row_item, width))
+
+            for row_items in zip_longest(*string_partition, fillvalue=""):
+                row_item_list = []
+                for index, row_item in enumerate(row_items):
+                    left_pad = table.columns._pad_character * lpw[index]
+                    right_pad = table.columns._pad_character * rpw[index]
+                    row_item_list.append(left_pad + row_item + right_pad)
+                result.append(row_item_list)
+
+        return [[""] * len(table.columns)] if len(result) == 0 else result
+
+    def _clamp_string(self, row_item, index, delimiter=""):
+        """Clamp `row_item` to fit in column referred by index.
+
+        This method considers padding and appends the delimiter if `row_item`
+        needs to be truncated.
+
+        Parameters
+        ----------
+        row_item: str
+            String which should be clamped.
+
+        index: int
+            Index of the column `row_item` belongs to.
+
+        delimiter: str
+            String which is to be appended to the clamped string.
+
+        Returns
+        -------
+        str
+            The modified string which fits in it's column.
+        """
+        lpw, rpw = self._get_padding()
+        width = self._table.columns.width[index] - lpw[index] - rpw[index]
+
+        if termwidth(row_item) <= width:
+            return row_item
+        else:
+            if width - len(delimiter) >= 0:
+                clamped_string = (
+                    textwrap(row_item, width - len(delimiter))[0] + delimiter
+                )
+            else:
+                clamped_string = delimiter[:width]
+            return clamped_string
+
+    def _get_string(
+        self,
+        align=None,
+        mask=None,
+        draw_left_border=True,
+        draw_right_border=True,
+    ):
+        """Return a string representation of a row."""
+
+        rows = []
+
+        table = self._table
+        width = table.columns.width
+        sign = table.sign
+
+        if align is None:
+            align = table.columns.alignment
+
+        if mask is None:
+            mask = [True] * len(table.columns)
+
+        lpw, rpw = self._get_padding()
+
+        string = []
+        for i, item in enumerate(self._value):
+            if isinstance(item, type(table)):
+                # temporarily change the max width of the table
+                curr_maxwidth = item.maxwidth
+                item.maxwidth = width[i] - lpw[i] - rpw[i]
+                rows.append(
+                    pre_process(
+                        item,
+                        table.detect_numerics,
+                        table.precision,
+                        sign.value,
+                    ).split("\n")
+                )
+                item.maxwidth = curr_maxwidth
+            else:
+                rows.append(
+                    pre_process(
+                        item,
+                        table.detect_numerics,
+                        table.precision,
+                        sign.value,
+                    ).split("\n")
+                )
+        for row in map(list, zip_longest(*rows, fillvalue="")):
+            for i in range(len(row)):
+                row[i] = pre_process(
+                    row[i], table.detect_numerics, table.precision, sign.value,
+                )
+            for row_ in self._clamp_row(row):
+                for i in range(len(table.columns)):
+                    # str.format method doesn't work for multibyte strings
+                    # hence, we need to manually align the texts instead
+                    # of using the align property of the str.format method
+                    pad_len = width[i] - termwidth(row_[i])
+                    if align[i].value == "<":
+                        right_pad = " " * pad_len
+                        row_[i] = to_unicode(row_[i]) + right_pad
+                    elif align[i].value == ">":
+                        left_pad = " " * pad_len
+                        row_[i] = left_pad + to_unicode(row_[i])
+                    else:
+                        left_pad = " " * (pad_len // 2)
+                        right_pad = " " * (pad_len - pad_len // 2)
+                        row_[i] = left_pad + to_unicode(row_[i]) + right_pad
+                content = []
+                for j, item in enumerate(row_):
+                    if j > 0:
+                        content.append(
+                            table.column_separator_char
+                            if (mask[j - 1] or mask[j])
+                            else " " * termwidth(table.column_separator_char)
+                        )
+                    content.append(item)
+                # content = table.column_separator_char.join(row_)
+                content = "".join(content)
+                content = (
+                    table.left_border_char
+                    if mask[0]
+                    else " " * termwidth(table.left_border_char)
+                ) + content
+                content += (
+                    table.right_border_char
+                    if mask[-1]
+                    else " " * termwidth(table.right_border_char)
+                )
+                string.append(content)
+        return "\n".join(string)
+
+    def __str__(self):
+        return self._get_string()
+
+
+class BTColumnData(BTBaseColumn):
+    pass
+
+
+class BTRowCollection(object):
+    def __init__(self, table):
+        self._table = table
+        self._reset_state(0)
+
+    @property
+    def _table(self):
+        return self._table_ref()
+
+    @_table.setter
+    def _table(self, value):
+        self._table_ref = weakref.ref(value)
+
+    def _reset_state(self, nrow):
+        self._table._data = type(self._table._data)(
+            self._table,
+            [
+                BTRowData(self._table, [None] * self._table._ncol)
+                for i in range(nrow)
+            ],
+        )
+        self.header = BTRowHeader(self._table, [None] * nrow)
+
+    @property
+    def header(self):
+        return self._header
+
+    @header.setter
+    def header(self, value):
+        self._header = BTRowHeader(self._table, value)
+
+    def _canonical_key(self, key):
+        if isinstance(key, (int, slice)):
+            return key
+        elif isinstance(key, basestring):
+            return self.header.index(key)
+        raise TypeError(
+            ("row indices must be int, str or slices, not {}").format(
+                type(key).__name__
+            )
+        )
+
+    def __len__(self):
+        return len(self._table._data)
+
+    def __getitem__(self, key):
+        """Get a particular row, or a new table by slicing.
+
+        Parameters
+        ----------
+        key : int, slice, str
+            If key is an `int`, returns a row at index `key`.
+            If key is an `str`, returns the first row with heading `key`.
+            If key is a slice object, returns a new sliced table.
+
+        Raises
+        ------
+        TypeError
+            If key is not of type int, slice or str.
+        IndexError
+            If `int` index is out of range.
+        KeyError
+            If `str` key is not found in header.
+        """
+        if isinstance(key, slice):
+            new_table = copy.deepcopy(self._table)
+            new_table.rows.clear()
+            new_table.rows.header = self._table.rows.header[key]
+            for i, r in enumerate(self._table._data[key]):
+                new_table.rows[i] = r.value
+            return new_table
+        if isinstance(key, (int, basestring)):
+            return self._table._data[key]
+        raise TypeError(
+            ("row indices must be int, str or a slice object, not {}").format(
+                type(key).__name__
+            )
+        )
+
+    def __delitem__(self, key):
+        """Delete a row, or multiple rows by slicing.
+
+        Parameters
+        ----------
+        key : int, slice, str
+            If key is an `int`, deletes a row at index `key`.
+            If key is an `str`, deletes the first row with heading `key`.
+            If key is a slice object, deletes multiple rows.
+
+        Raises
+        ------
+        TypeError
+            If key is not of type int, slice or str.
+        IndexError
+            If `int` key is out of range.
+        KeyError
+            If `str` key is not in header.
+        """
+        if isinstance(key, (int, basestring, slice)):
+            del self._table._data[key]
+            del self.header[key]
+        else:
+            raise TypeError(
+                (
+                    "row indices must be int, str or " "a slice object, not {}"
+                ).format(type(key).__name__)
+            )
+
+    def __setitem__(self, key, value):
+        """Update a row, or multiple rows by slicing.
+
+        Parameters
+        ----------
+        key : int, slice, str
+            If key is an `int`, updates a row.
+            If key is an `str`, updates the first row with heading `key`.
+            If key is a slice object, updates multiple rows.
+
+        Raises
+        ------
+        TypeError
+            If key is not of type int, slice or str.
+        IndexError
+            If `int` key is out of range.
+        KeyError
+            If `str` key is not in header.
+        """
+        if isinstance(key, (int, basestring)):
+            self._table._data[key] = BTRowData(self._table, value)
+        elif isinstance(key, slice):
+            value = [list(row) for row in value]
+            if len(self._table.columns) == 0:
+                self._table.columns._initialize(len(value[0]))
+            self._table._data[key] = [
+                BTRowData(self._table, row) for row in value
+            ]
+        else:
+            raise TypeError("key must be int, str or a slice object")
+
+    def __contains__(self, key):
+        if isinstance(key, basestring):
+            return key in self.header
+        elif isinstance(key, Iterable):
+            return key in self._table._data
+        else:
+            raise TypeError(
+                ("'key' must be str or Iterable, " "not {}").format(
+                    type(key).__name__
+                )
+            )
+
+    def __iter__(self):
+        return BTCollectionIterator(self)
+
+    def __repr__(self):
+        return repr(self._table._data)
+
+    def __str__(self):
+        return str(self._table._data)
+
+    def reverse(self):
+        """Reverse the table row-wise *IN PLACE*."""
+        self._table._data._reverse()
+
+    def pop(self, index=-1):
+        """Remove and return row at index (default last).
+
+        Parameters
+        ----------
+        index : int, str
+            index or heading of the row. Normal list rules apply.
+        """
+        if not isinstance(index, (int, basestring)):
+            raise TypeError(
+                ("row index must be int or str, " "not {}").format(
+                    type(index).__name__
+                )
+            )
+        if len(self._table._data) == 0:
+            raise IndexError("pop from empty table")
+        else:
+            res = self._table._data._pop(index)
+            self.header._pop(index)
+            return res
+
+    def insert(self, index, row, header=None):
+        """Insert a row before index in the table.
+
+        Parameters
+        ----------
+        index : int
+            List index rules apply
+
+        row : iterable
+            Any iterable of appropriate length.
+
+        header : str, optional
+            Heading of the row
+
+        Raises
+        ------
+        TypeError:
+            If `row` is not an iterable.
+
+        ValueError:
+            If size of `row` is inconsistent with the current number
+            of columns.
+        """
+        if self._table._ncol == 0:
+            row = list(row)
+            self._table.columns._reset_state(len(row))
+        self.header._insert(index, header)
+        self._table._data._insert(index, BTRowData(self._table, row))
+
+    def append(self, row, header=None):
+        """Append a row to end of the table.
+
+        Parameters
+        ----------
+        row : iterable
+            Any iterable of appropriate length.
+
+        header : str, optional
+            Heading of the row
+
+        """
+        self.insert(len(self), row, header)
+
+    def update(self, key, value):
+        """Update row(s) identified with `key` in the table.
+
+        `key` can be a index or a slice object.
+
+        Parameters
+        ----------
+        key : int or slice
+            index of the row, or a slice object.
+
+        value : iterable
+            If an index is specified, `value` should be an iterable
+            of appropriate length. Instead if a slice object is
+            passed as key, value should be an iterable of rows.
+
+        Raises
+        ------
+        IndexError:
+            If index specified is out of range.
+
+        TypeError:
+            If `value` is of incorrect type.
+
+        ValueError:
+            If length of row does not matches number of columns.
+        """
+        self[key] = value
+
+    def clear(self):
+        self._reset_state(0)
+
+    def sort(self, key, reverse=False):
+        """Stable sort of the table *IN-PLACE* with respect to a column.
+
+        Parameters
+        ----------
+        key: int, str
+            index or header of the column. Normal list rules apply.
+        reverse : bool
+            If `True` then table is sorted as if each comparison was reversed.
+        """
+        if isinstance(key, (int, basestring)):
+            key = operator.itemgetter(key)
+        elif callable(key):
+            pass
+        else:
+            raise TypeError(
+                "'key' must either be 'int' or 'str' or a 'callable'"
+            )
+
+        indices = sorted(
+            range(len(self)),
+            key=lambda x: key(self._table._data[x]),
+            reverse=reverse,
+        )
+        self._table._data._sort(key=key, reverse=reverse)
+        self.header = [self.header[i] for i in indices]
+
+    def filter(self, key):
+        """Return a copy of the table with only those rows which satisfy a
+        certain condition.
+
+        Returns
+        -------
+        BeautifulTable:
+            Filtered copy of the BeautifulTable instance.
+        """
+        new_table = self._table.copy()
+        new_table.rows.clear()
+        for row in filter(key, self):
+            new_table.rows.append(row)
+        return new_table
+
+
+class BTCollectionIterator(object):
+    def __init__(self, collection):
+        self._collection = collection
+        self._index = -1
+
+    def __iter__(self):
+        return self
+
+    def __next__(self):
+        self._index += 1
+        if self._index == len(self._collection):
+            raise StopIteration
+        return self._collection[self._index]
+
+
+class BTColumnCollection(object):
+    def __init__(self, table, default_alignment, default_padding):
+        self._table = table
+        self._width_exceed_policy = enums.WEP_WRAP
+        self._pad_character = " "
+        self.default_alignment = default_alignment
+        self.default_padding = default_padding
+
+        self._reset_state(0)
+
+    @property
+    def _table(self):
+        return self._table_ref()
+
+    @_table.setter
+    def _table(self, value):
+        self._table_ref = weakref.ref(value)
+
+    @property
+    def padding(self):
+        """Set width for left and rigth padding of the columns of the table."""
+        raise AttributeError(
+            "cannot read attribute 'padding'. use 'padding_{left|right}'"
+        )
+
+    @padding.setter
+    def padding(self, value):
+        self.padding_left = value
+        self.padding_right = value
+
+    def _reset_state(self, ncol):
+        self._table._ncol = ncol
+        self._header = BTColumnHeader(self._table, [None] * ncol)
+        self._auto_width = True
+        self._alignment = AlignmentMetaData(
+            self._table, [self.default_alignment] * ncol
+        )
+        self._width = NonNegativeIntegerMetaData(self._table, [0] * ncol)
+        self._padding_left = NonNegativeIntegerMetaData(
+            self._table, [self.default_padding] * ncol
+        )
+        self._padding_right = NonNegativeIntegerMetaData(
+            self._table, [self.default_padding] * ncol
+        )
+        self._table._data = type(self._table._data)(
+            self._table,
+            [
+                BTRowData(self._table, [None] * ncol)
+                for i in range(len(self._table._data))
+            ],
+        )
+
+    def _canonical_key(self, key):
+        if isinstance(key, (int, slice)):
+            return key
+        elif isinstance(key, basestring):
+            return self.header.index(key)
+        raise TypeError(
+            ("column indices must be int, str or slices, not {}").format(
+                type(key).__name__
+            )
+        )
+
+    @property
+    def header(self):
+        """get/set headings for the columns of the table.
+
+        It can be any iterable with all members an instance of `str` or None.
+        """
+        return self._header
+
+    @header.setter
+    def header(self, value):
+        self._header = BTColumnHeader(self._table, value)
+
+    @property
+    def alignment(self):
+        """get/set alignment of the columns of the table.
+
+        It can be any iterable containing only the following:
+
+        * beautifultable.ALIGN_LEFT
+        * beautifultable.ALIGN_CENTER
+        * beautifultable.ALIGN_RIGHT
+        """
+        return self._alignment
+
+    @alignment.setter
+    def alignment(self, value):
+        if isinstance(value, enums.Alignment):
+            value = [value] * len(self)
+        self._alignment = AlignmentMetaData(self._table, value)
+
+    @property
+    def width(self):
+        """get/set width for the columns of the table.
+
+        Width of the column specifies the max number of characters
+        a column can contain. Larger characters are handled according to
+        `width_exceed_policy`. This can be one of `'auto'`, a non-negative
+        integer or an iterable of the same length as the number of columns.
+        If set to anything other than 'auto', the user is responsible for
+        updating it if new columns are added or existing ones are updated.
+        """
+        return self._width
+
+    @width.setter
+    def width(self, value):
+        if isinstance(value, str):
+            if value == "auto":
+                self._auto_width = True
+                return
+            raise ValueError("Invalid value '{}'".format(value))
+        if isinstance(value, int):
+            value = [value] * len(self)
+        self._width = NonNegativeIntegerMetaData(self._table, value)
+        self._auto_width = False
+
+    @property
+    def padding_left(self):
+        """get/set width for left padding of the columns of the table.
+
+        Left Width of the padding specifies the number of characters
+        on the left of a column reserved for padding. By Default It is 1.
+        """
+        return self._padding_left
+
+    @padding_left.setter
+    def padding_left(self, value):
+        if isinstance(value, int):
+            value = [value] * len(self)
+        self._padding_left = NonNegativeIntegerMetaData(self._table, value)
+
+    @property
+    def padding_right(self):
+        """get/set width for right padding of the columns of the table.
+
+        Right Width of the padding specifies the number of characters
+        on the rigth of a column reserved for padding. By default It is 1.
+        """
+        return self._padding_right
+
+    @padding_right.setter
+    def padding_right(self, value):
+        if isinstance(value, int):
+            value = [value] * len(self)
+        self._padding_right = NonNegativeIntegerMetaData(self._table, value)
+
+    @property
+    def width_exceed_policy(self):
+        """Attribute to control how exceeding column width should be handled.
+
+        It can be one of the following:
+
+        ============================  =========================================
+         Option                        Meaning
+        ============================  =========================================
+         beautifulbable.WEP_WRAP       An item is wrapped so every line fits
+                                       within it's column width.
+
+         beautifultable.WEP_STRIP      An item is stripped to fit in it's
+                                       column.
+
+         beautifultable.WEP_ELLIPSIS   An item is stripped to fit in it's
+                                       column and appended with ...(Ellipsis).
+        ============================  =========================================
+        """
+        return self._width_exceed_policy
+
+    @width_exceed_policy.setter
+    def width_exceed_policy(self, value):
+        if not isinstance(value, enums.WidthExceedPolicy):
+            allowed = (
+                "{}.{}".format(type(self).__name__, i.name)
+                for i in enums.WidthExceedPolicy
+            )
+            error_msg = (
+                "allowed values for width_exceed_policy are: "
+                + ", ".join(allowed)
+            )
+            raise ValueError(error_msg)
+        self._width_exceed_policy = value
+
+    @property
+    def default_alignment(self):
+        """Attribute to control the alignment of newly created columns.
+
+        It can be one of the following:
+
+        ============================  =========================================
+         Option                        Meaning
+        ============================  =========================================
+         beautifultable.ALIGN_LEFT     New columns are left aligned.
+
+         beautifultable.ALIGN_CENTER   New columns are center aligned.
+
+         beautifultable.ALIGN_RIGHT    New columns are right aligned.
+        ============================  =========================================
+        """
+        return self._default_alignment
+
+    @default_alignment.setter
+    def default_alignment(self, value):
+        if not isinstance(value, enums.Alignment):
+            allowed = (
+                "{}.{}".format(type(self).__name__, i.name)
+                for i in enums.Alignment
+            )
+            error_msg = (
+                "allowed values for default_alignment are: "
+                + ", ".join(allowed)
+            )
+            raise ValueError(error_msg)
+        self._default_alignment = value
+
+    @property
+    def default_padding(self):
+        """Initial value for Left and Right padding widths for new columns."""
+        return self._default_padding
+
+    @default_padding.setter
+    def default_padding(self, value):
+        if not isinstance(value, int):
+            raise TypeError("default_padding must be an integer")
+        elif value < 0:
+            raise ValueError("default_padding must be a non-negative integer")
+        else:
+            self._default_padding = value
+
+    def __len__(self):
+        return self._table._ncol
+
+    def __getitem__(self, key):
+        """Get a column, or a new table by slicing.
+
+        Parameters
+        ----------
+
+        key : int, slice, str
+            If key is an `int`, returns column at index `key`.
+            If key is an `str`, returns first column with heading `key`.
+            If key is a slice object, returns a new sliced table.
+
+        Raises
+        ------
+
+        TypeError
+            If key is not of type int, slice or str.
+        IndexError
+            If `int` key is out of range.
+        KeyError
+            If `str` key is not in header.
+        """
+        if isinstance(key, int):
+            pass
+        elif isinstance(key, slice):
+            new_table = copy.deepcopy(self._table)
+
+            new_table.columns.clear()
+            new_table.columns.header = self.header[key]
+            new_table.columns.alignment = self.alignment[key]
+            new_table.columns.padding_left = self.padding_left[key]
+            new_table.columns.padding_right = self.padding_right[key]
+            new_table.columns.width = self.width[key]
+            new_table.columns._auto_width = self._auto_width
+            for i, r in enumerate(self._table._data):
+                new_table.rows[i] = r.value[key]
+            return new_table
+        elif isinstance(key, basestring):
+            key = self.header.index(key)
+        else:
+            raise TypeError(
+                (
+                    "column indices must be integers, strings or "
+                    "slices, not {}"
+                ).format(type(key).__name__)
+            )
+        return BTColumnData(
+            self._table, [row[key] for row in self._table._data]
+        )
+
+    def __delitem__(self, key):
+        """Delete a column, or multiple columns by slicing.
+
+        Parameters
+        ----------
+
+        key : int, slice, str
+            If key is an `int`, deletes column at index `key`.
+            If key is a slice object, deletes multiple columns.
+            If key is an `str`, deletes the first column with heading `key`
+
+        Raises
+        ------
+
+        TypeError
+            If key is not of type int, slice or str.
+        IndexError
+            If `int` key is out of range.
+        KeyError
+            If `str` key is not in header.
+        """
+        if isinstance(key, (int, basestring, slice)):
+            key = self._canonical_key(key)
+
+            del self.alignment[key]
+            del self.width[key]
+            del self.padding_left[key]
+            del self.padding_right[key]
+            for row in self._table.rows:
+                del row[key]
+            del self.header[key]
+            if self.header.alignment is not None:
+                del self.header.alignment[key]
+            self._table._ncol = len(self.header)
+            if self._table._ncol == 0:
+                del self._table.rows[:]
+        else:
+            raise TypeError(
+                ("table indices must be int, str or " "slices, not {}").format(
+                    type(key).__name__
+                )
+            )
+
+    def __setitem__(self, key, value):
+        """Update a column, or multiple columns by slicing.
+
+        Parameters
+        ----------
+
+        key : int, slice, str
+            If key is an `int`, updates column at index `key`.
+            If key is an `str`, updates first column with heading `key`.
+            If key is a slice object, updates multiple columns.
+
+        Raises
+        ------
+
+        TypeError
+            If key is not of type int, slice or str.
+        IndexError
+            If `int` key is out of range.
+        KeyError
+            If `str` key is not in header
+        """
+        if not isinstance(key, (int, basestring, slice)):
+            raise TypeError(
+                "column indices must be of type int, str or a slice object"
+            )
+        for row, new_item in zip(self._table.rows, value):
+            row[key] = new_item
+
+    def __contains__(self, key):
+        if isinstance(key, basestring):
+            return key in self.header
+        elif isinstance(key, Iterable):
+            key = list(key)
+            return any(key == column for column in self)
+        else:
+            raise TypeError(
+                ("'key' must be str or Iterable, " "not {}").format(
+                    type(key).__name__
+                )
+            )
+
+    def __iter__(self):
+        return BTCollectionIterator(self)
+
+    def __repr__(self):
+        return repr(self._table)
+
+    def __str__(self):
+        return str(self._table._data)
+
+    def clear(self):
+        self._reset_state(0)
+
+    def pop(self, index=-1):
+        """Remove and return column at index (default last).
+
+        Parameters
+        ----------
+        index : int, str
+            index of the column, or the header of the column.
+            If index is specified, then normal list rules apply.
+
+        Raises
+        ------
+        TypeError:
+            If index is not an instance of `int`, or `str`.
+
+        IndexError:
+            If Table is empty.
+        """
+        if not isinstance(index, (int, basestring)):
+            raise TypeError(
+                ("column index must be int or str, " "not {}").format(
+                    type(index).__name__
+                )
+            )
+        if self._table._ncol == 0:
+            raise IndexError("pop from empty table")
+        else:
+            res = []
+            index = self._canonical_key(index)
+            for row in self._table.rows:
+                res.append(row._pop(index))
+            res = BTColumnData(self._table, res)
+            self.alignment._pop(index)
+            self.width._pop(index)
+            self.padding_left._pop(index)
+            self.padding_right._pop(index)
+            self.header._pop(index)
+
+            self._table._ncol = len(self.header)
+            if self._table._ncol == 0:
+                del self._table.rows[:]
+            return res
+
+    def update(self, key, value):
+        """Update a column named `header` in the table.
+
+        If length of column is smaller than number of rows, lets say
+        `k`, only the first `k` values in the column is updated.
+
+        Parameters
+        ----------
+        key : int, str
+            If `key` is int, column at index `key` is updated.
+            If `key` is str, the first column with heading `key` is updated.
+
+        value : iterable
+            Any iterable of appropriate length.
+
+        Raises
+        ------
+        TypeError:
+            If length of `column` is shorter than number of rows.
+
+        ValueError:
+            If no column exists with heading `header`.
+        """
+        self[key] = value
+
+    def insert(
+        self,
+        index,
+        column,
+        header=None,
+        padding_left=None,
+        padding_right=None,
+        alignment=None,
+    ):
+        """Insert a column before `index` in the table.
+
+        If length of column is bigger than number of rows, lets say
+        `k`, only the first `k` values of `column` is considered.
+        If column is shorter than 'k', ValueError is raised.
+
+        Note that Table remains in consistent state even if column
+        is too short. Any changes made by this method is rolled back
+        before raising the exception.
+
+        Parameters
+        ----------
+        index : int
+            List index rules apply.
+
+        column : iterable
+            Any iterable of appropriate length.
+
+        header : str, optional
+            Heading of the column.
+
+        padding_left : int, optional
+            Left padding of the column.
+
+        padding_right : int, optional
+            Right padding of the column.
+
+        alignment : Alignment, optional
+            alignment of the column.
+
+        Raises
+        ------
+        TypeError:
+            If `header` is not of type `str`.
+
+        ValueError:
+            If length of `column` is shorter than number of rows.
+        """
+        padding_left = (
+            self.default_padding if padding_left is None else padding_left
+        )
+        padding_right = (
+            self.default_padding if padding_right is None else padding_right
+        )
+        alignment = self.default_alignment if alignment is None else alignment
+        if not isinstance(padding_left, int):
+            raise TypeError(
+                "'padding_left' should be of type 'int' not '{}'".format(
+                    type(padding_left).__name__
+                )
+            )
+        if not isinstance(padding_right, int):
+            raise TypeError(
+                "'padding_right' should be of type 'int' not '{}'".format(
+                    type(padding_right).__name__
+                )
+            )
+        if not isinstance(alignment, enums.Alignment):
+            raise TypeError(
+                "alignment should be of type '{}' not '{}'".format(
+                    enums.Alignment.__name__, type(alignment).__name__
+                )
+            )
+
+        if self._table._ncol == 0:
+            self.header = [header]
+            self.padding_left = [padding_left]
+            self.padding_right = [padding_right]
+            self.alignment = [alignment]
+            self._table._data = [BTRowData(self._table, [i]) for i in column]
+        else:
+            if (not isinstance(header, basestring)) and (header is not None):
+                raise TypeError(
+                    "header must be of type 'str' not '{}'".format(
+                        type(header).__name__
+                    )
+                )
+            column_length = 0
+            for row, new_item in zip(self._table.rows, column):
+                row._insert(index, new_item)
+                column_length += 1
+            if column_length == len(self._table.rows):
+                self._table._ncol += 1
+                self.header._insert(index, header)
+                self.width._insert(index, 0)
+                self.alignment._insert(index, alignment)
+                self.padding_left._insert(index, padding_left)
+                self.padding_right._insert(index, padding_right)
+                if self.header.alignment is not None:
+                    self.header.alignment._insert(index, alignment)
+            else:
+                # Roll back changes so that table remains in consistent state
+                for j in range(column_length, -1, -1):
+                    self._table.rows[j]._pop(index)
+                raise ValueError(
+                    (
+                        "length of 'column' should be atleast {}, " "got {}"
+                    ).format(len(self._table.rows), column_length)
+                )
+
+    def append(
+        self,
+        column,
+        header=None,
+        padding_left=None,
+        padding_right=None,
+        alignment=None,
+    ):
+        """Append a column to end of the table.
+
+        Parameters
+        ----------
+        column : iterable
+            Any iterable of appropriate length.
+
+        header : str, optional
+            Heading of the column
+
+        padding_left : int, optional
+            Left padding of the column
+
+        padding_right : int,  optional
+            Right padding of the column
+
+        alignment : Alignment, optional
+            alignment of the column
+        """
+        self.insert(
+            self._table._ncol,
+            column,
+            header,
+            padding_left,
+            padding_right,
+            alignment,
+        )
diff --git a/beautifultable/meta.py b/beautifultable/meta.py
index 8a9eec3..3ad6005 100644
--- a/beautifultable/meta.py
+++ b/beautifultable/meta.py
@@ -1,22 +1,22 @@
-from .base import BaseRow
+from .base import BTBaseRow
 from .enums import Alignment
 
 
-class TableMetaData(BaseRow):
+class MetaData(BTBaseRow):
     def __init__(self, table, row):
         for i in row:
             self.validate(i)
-        super(TableMetaData, self).__init__(table, row)
+        super(MetaData, self).__init__(table, row)
 
     def __setitem__(self, key, value):
         self.validate(value)
-        super(TableMetaData, self).__setitem__(key, value)
+        super(MetaData, self).__setitem__(key, value)
 
     def validate(self, value):
         pass
 
 
-class AlignmentMetaData(TableMetaData):
+class AlignmentMetaData(MetaData):
     def validate(self, value):
         if not isinstance(value, Alignment):
             allowed = (
@@ -30,7 +30,7 @@ class AlignmentMetaData(TableMetaData):
             raise TypeError(error_msg)
 
 
-class PositiveIntegerMetaData(TableMetaData):
+class NonNegativeIntegerMetaData(MetaData):
     def validate(self, value):
         if isinstance(value, int) and value >= 0:
             pass
diff --git a/beautifultable/rows.py b/beautifultable/rows.py
deleted file mode 100644
index 2a80e2c..0000000
--- a/beautifultable/rows.py
+++ /dev/null
@@ -1,184 +0,0 @@
-from __future__ import unicode_literals
-from .utils import get_output_str, termwidth, textwrap
-from .base import BaseRow
-from .enums import WidthExceedPolicy
-from .compat import basestring, to_unicode, zip_longest
-
-
-class RowData(BaseRow):
-    def _get_row_within_width(self, row):
-        """Process a row so that it is clamped by column_width.
-
-        Parameters
-        ----------
-        row : array_like
-             A single row.
-
-        Returns
-        -------
-        list of list:
-            List representation of the `row` after it has been processed
-            according to width exceed policy.
-        """
-        table = self._table
-        lpw, rpw = table.left_padding_widths, table.right_padding_widths
-        wep = table.width_exceed_policy
-
-        list_of_rows = []
-
-        if (
-            wep is WidthExceedPolicy.WEP_STRIP
-            or wep is WidthExceedPolicy.WEP_ELLIPSIS
-        ):
-
-            # Let's strip the row
-            delimiter = "" if wep is WidthExceedPolicy.WEP_STRIP else "..."
-            row_item_list = []
-            for index, row_item in enumerate(row):
-                left_pad = table._column_pad * lpw[index]
-                right_pad = table._column_pad * rpw[index]
-                clmp_str = (
-                    left_pad
-                    + self._clamp_string(row_item, index, delimiter)
-                    + right_pad
-                )
-                row_item_list.append(clmp_str)
-            list_of_rows.append(row_item_list)
-        elif wep is WidthExceedPolicy.WEP_WRAP:
-
-            # Let's wrap the row
-            string_partition = []
-
-            for index, row_item in enumerate(row):
-                width = table.column_widths[index] - lpw[index] - rpw[index]
-                string_partition.append(textwrap(row_item, width))
-
-            for row_items in zip_longest(*string_partition, fillvalue=""):
-                row_item_list = []
-                for index, row_item in enumerate(row_items):
-                    left_pad = table._column_pad * lpw[index]
-                    right_pad = table._column_pad * rpw[index]
-                    row_item_list.append(left_pad + row_item + right_pad)
-                list_of_rows.append(row_item_list)
-
-        if len(list_of_rows) == 0:
-            return [[""] * table.column_count]
-        else:
-            return list_of_rows
-
-    def _clamp_string(self, row_item, column_index, delimiter=""):
-        """Clamp `row_item` to fit in column referred by column_index.
-
-        This method considers padding and appends the delimiter if `row_item`
-        needs to be truncated.
-
-        Parameters
-        ----------
-        row_item: str
-            String which should be clamped.
-
-        column_index: int
-            Index of the column `row_item` belongs to.
-
-        delimiter: str
-            String which is to be appended to the clamped string.
-
-        Returns
-        -------
-        str
-            The modified string which fits in it's column.
-        """
-        width = (
-            self._table.column_widths[column_index]
-            - self._table.left_padding_widths[column_index]
-            - self._table.right_padding_widths[column_index]
-        )
-
-        if termwidth(row_item) <= width:
-            return row_item
-        else:
-            if width - len(delimiter) >= 0:
-                clamped_string = (
-                    textwrap(row_item, width - len(delimiter))[0] + delimiter
-                )
-            else:
-                clamped_string = delimiter[:width]
-            return clamped_string
-
-    def __str__(self):
-        """Return a string representation of a row."""
-        rows = []
-        table = self._table
-        width = table.column_widths
-        align = table.column_alignments
-        sign = table.sign_mode
-        lpw = table.left_padding_widths
-        rpw = table.right_padding_widths
-        string = []
-        for i, item in enumerate(self._row):
-            if isinstance(item, type(table)):
-                # temporarily change the max width of the table
-                curr_max_width = item.max_table_width
-                item.max_table_width = width[i] - lpw[i] - rpw[i]
-                rows.append(to_unicode(item).split("\n"))
-                item.max_table_width = curr_max_width
-            else:
-                rows.append(to_unicode(item).split("\n"))
-        for row in map(list, zip_longest(*rows, fillvalue="")):
-            for i in range(len(row)):
-                row[i] = get_output_str(
-                    row[i],
-                    table.detect_numerics,
-                    table.numeric_precision,
-                    sign.value,
-                )
-            list_of_rows = self._get_row_within_width(row)
-            for row_ in list_of_rows:
-                for i in range(table.column_count):
-                    # str.format method doesn't work for multibyte strings
-                    # hence, we need to manually align the texts instead
-                    # of using the align property of the str.format method
-                    pad_len = width[i] - termwidth(row_[i])
-                    if align[i].value == "<":
-                        right_pad = " " * pad_len
-                        row_[i] = to_unicode(row_[i]) + right_pad
-                    elif align[i].value == ">":
-                        left_pad = " " * pad_len
-                        row_[i] = left_pad + to_unicode(row_[i])
-                    else:
-                        left_pad = " " * (pad_len // 2)
-                        right_pad = " " * (pad_len - pad_len // 2)
-                        row_[i] = left_pad + to_unicode(row_[i]) + right_pad
-                content = table.column_separator_char.join(row_)
-                content = table.left_border_char + content
-                content += table.right_border_char
-                string.append(content)
-        return "\n".join(string)
-
-
-class HeaderData(RowData):
-    def __init__(self, table, row):
-        for i in row:
-            self.validate(i)
-        RowData.__init__(self, table, row)
-
-    def __getitem__(self, key):
-        return self._row[key]
-
-    def __setitem__(self, key, value):
-        self.validate(value)
-        if not isinstance(key, int):
-            raise TypeError(
-                ("header indices must be integers, " "not {}").format(
-                    type(key).__name__
-                )
-            )
-        self._row[key] = value
-
-    def validate(self, value):
-        if not isinstance(value, basestring):
-            raise TypeError(
-                ("header must be of type 'str', " "got {}").format(
-                    type(value).__name__
-                )
-            )
diff --git a/beautifultable/utils.py b/beautifultable/utils.py
index b3331e9..da0a87a 100644
--- a/beautifultable/utils.py
+++ b/beautifultable/utils.py
@@ -1,14 +1,13 @@
 """Module containing some utility methods"""
 
 import warnings
-
+import functools
 
 from .ansi import ANSIMultiByteString
 from .compat import to_unicode
-from .exceptions import BeautifulTableDeprecationWarning
 
 
-def _convert_to_numeric(item):
+def to_numeric(item):
     """
     Helper method to convert a string to float or int if possible.
 
@@ -37,10 +36,12 @@ def _convert_to_numeric(item):
         return num
 
 
-def get_output_str(item, detect_numerics, precision, sign_value):
+def pre_process(item, detect_numerics, precision, sign_value):
     """Returns the final string which should be displayed"""
+    if item is None:
+        return ""
     if detect_numerics:
-        item = _convert_to_numeric(item)
+        item = to_numeric(item)
     if isinstance(item, float):
         item = round(item, precision)
     try:
@@ -61,10 +62,69 @@ def textwrap(item, width):
     return obj.wrap(width)
 
 
-def raise_suppressed(exp):
-    exp.__cause__ = None
-    raise exp
-
-
-def deprecation(message):
-    warnings.warn(message, BeautifulTableDeprecationWarning)
+def deprecation_message(
+    old_name, deprecated_in, removed_in, extra_msg
+):  # pragma: no cover
+    return (
+        "'{}' has been deprecated in 'v{}' and will be removed in 'v{}'. "
+        "{}".format(old_name, deprecated_in, removed_in, extra_msg)
+    )
+
+
+def deprecated(
+    deprecated_in, removed_in, replacement=None, details=None,
+):  # pragma: no cover
+    def decorator(f):
+        @functools.wraps(f)
+        def wrapper(*args, **kwds):
+            nonlocal details
+            if not details:
+                if replacement:
+                    details = replacement.__qualname__
+                    details = details.replace(
+                        "BTColumns", "BeautifulTable.columns",
+                    )
+                    details = details.replace("BTRows", "BeautifulTable.rows",)
+                    details = details.replace(
+                        "BTColumnHeader", "BeautifulTable.columns.header",
+                    )
+                    details = details.replace(
+                        "BTRowHeader", "BeautifulTable.rows.header",
+                    )
+                    details = "Use '{}' instead.".format(details)
+                else:
+                    details = ""
+            message = deprecation_message(
+                f.__qualname__, deprecated_in, removed_in, details,
+            )
+            if replacement:
+                f.__doc__ = "{}\n\n{}".format(replacement.__doc__, message)
+            warnings.warn(message, FutureWarning)
+            return f(*args, **kwds)
+
+        return wrapper
+
+    return decorator
+
+
+def deprecated_param(
+    deprecated_in, removed_in, old_name, new_name=None, details=None,
+):  # pragma: no cover
+    def decorator(f):
+        @functools.wraps(f)
+        def wrapper(*args, **kwargs):
+            nonlocal details
+            if not details:
+                details = (
+                    "Use '{}' instead.".format(new_name) if new_name else ""
+                )
+            message = deprecation_message(
+                old_name, deprecated_in, removed_in, details,
+            )
+            if old_name in kwargs:
+                warnings.warn(message, FutureWarning)
+            return f(*args, **kwargs)
+
+        return wrapper
+
+    return decorator
diff --git a/docs/quickstart.rst b/docs/quickstart.rst
index c003e80..4fd0a66 100644
--- a/docs/quickstart.rst
+++ b/docs/quickstart.rst
@@ -6,98 +6,118 @@ Quickstart
 Building the Table
 =========================================================================
 
-Building a table is very easy. You can append rows and columns
-in the table. Let's create our first :class:`~.BeautifulTable`.
+Building a table is very easy. :class:`~.BeautifulTable` provides two views
+``rows`` and ``columns``. You can use them to modify their respective properties.
+
+Let's create our first table and add some rows.
 
 .. code:: python
 
    >>> from beautifultable import BeautifulTable
    >>> table = BeautifulTable()
-   >>> table.column_headers = ["name", "rank", "gender"]
-   >>> table.append_row(["Jacob", 1, "boy"])
-   >>> table.append_row(["Isabella", 1, "girl"])
-   >>> table.append_row(["Ethan", 2, "boy"])
-   >>> table.append_row(["Sophia", 2, "girl"])
-   >>> table.append_row(["Michael", 3, "boy"])
+   >>> table.rows.append(["Jacob", 1, "boy"])
+   >>> table.rows.append(["Isabella", 1, "girl"])
+   >>> table.rows.append(["Ethan", 2, "boy"])
+   >>> table.rows.append(["Sophia", 2, "girl"])
+   >>> table.rows.append(["Michael", 3, "boy"])
+   >>> table.columns.header = ["name", "rank", "gender"]
+   >>> table.rows.header = ["S1", "S2", "S3", "S4", "S5"]
    >>> print(table)
-   +----------+------+--------+
-   |   name   | rank | gender |
-   +----------+------+--------+
-   |  Jacob   |  1   |  boy   |
-   +----------+------+--------+
-   | Isabella |  1   |  girl  |
-   +----------+------+--------+
-   |  Ethan   |  2   |  boy   |
-   +----------+------+--------+
-   |  Sophia  |  2   |  girl  |
-   +----------+------+--------+
-   | Michael  |  3   |  boy   |
-   +----------+------+--------+
+   +----+----------+------+--------+
+   |    |   name   | rank | gender |
+   +----+----------+------+--------+
+   | S1 |  Jacob   |  1   |  boy   |
+   +----+----------+------+--------+
+   | S2 | Isabella |  1   |  girl  |
+   +----+----------+------+--------+
+   | S3 |  Ethan   |  2   |  boy   |
+   +----+----------+------+--------+
+   | S4 |  Sophia  |  2   |  girl  |
+   +----+----------+------+--------+
+   | S5 | Michael  |  3   |  boy   |
+   +----+----------+------+--------+
 
 We created our first table. Let's add some more data to it.
 But this time we will add a new column.
 
 .. code:: python
 
-   >>> table.append_column("year", ["2010", "2012", "2008", "2010", "2011"])
+   >>> table.columns.append(["2010", "2012", "2008", "2010", "2011"], header="year")
    >>> print(table)
-   +----------+------+--------+------+
-   |   name   | rank | gender | year |
-   +----------+------+--------+------+
-   |  Jacob   |  1   |  boy   | 2010 |
-   +----------+------+--------+------+
-   | Isabella |  1   |  girl  | 2012 |
-   +----------+------+--------+------+
-   |  Ethan   |  2   |  boy   | 2008 |
-   +----------+------+--------+------+
-   |  Sophia  |  2   |  girl  | 2010 |
-   +----------+------+--------+------+
-   | Michael  |  3   |  boy   | 2011 |
-   +----------+------+--------+------+
+   +----+----------+------+--------+------+
+   |    |   name   | rank | gender | year |
+   +----+----------+------+--------+------+
+   | S1 |  Jacob   |  1   |  boy   | 2010 |
+   +----+----------+------+--------+------+
+   | S2 | Isabella |  1   |  girl  | 2012 |
+   +----+----------+------+--------+------+
+   | S3 |  Ethan   |  2   |  boy   | 2008 |
+   +----+----------+------+--------+------+
+   | S4 |  Sophia  |  2   |  girl  | 2010 |
+   +----+----------+------+--------+------+
+   | S5 | Michael  |  3   |  boy   | 2011 |
+   +----+----------+------+--------+------+
 
 You can also build a :class:`~.BeautifulTable` using slicing. Slicing creates a
 new table with it's own copy of data. But it retains the properties
-of the original object.
+of the original object. You can slice both rows or columns.
+
+.. code:: python
+
+   >>> new_table = table.rows[:3]
+   >>> print(new_table)
+   +----+----------+------+--------+------+
+   |    |   name   | rank | gender | year |
+   +----+----------+------+--------+------+
+   | S1 |  Jacob   |  1   |  boy   | 2010 |
+   +----+----------+------+--------+------+
+   | S2 | Isabella |  1   |  girl  | 2012 |
+   +----+----------+------+--------+------+
+   | S3 |  Ethan   |  2   |  boy   | 2008 |
+   +----+----------+------+--------+------+
+
 
 .. code:: python
 
-   >>> new_table = table[:3]
+   >>> new_table = table.columns[:3]
    >>> print(new_table)
-   +----------+------+--------+------+
-   |   name   | rank | gender | year |
-   +----------+------+--------+------+
-   |  Jacob   |  1   |  boy   | 2010 |
-   +----------+------+--------+------+
-   | Isabella |  1   |  girl  | 2012 |
-   +----------+------+--------+------+
-   |  Ethan   |  2   |  boy   | 2008 |
-   +----------+------+--------+------+
+   +----+----------+------+--------+
+   |    |   name   | rank | gender |
+   +----+----------+------+--------+
+   | S1 |  Jacob   |  1   |  boy   |
+   +----+----------+------+--------+
+   | S2 | Isabella |  1   |  girl  |
+   +----+----------+------+--------+
+   | S3 |  Ethan   |  2   |  boy   |
+   +----+----------+------+--------+
+   | S4 |  Sophia  |  2   |  girl  |
+   +----+----------+------+--------+
+   | S5 | Michael  |  3   |  boy   |
+   +----+----------+------+--------+
 
 As you can see how easy it is to create a Table with **beautifultable**.
-Now lets move on to see some common use cases. Note that not all
-features are described here. See the API Documentation to get a
-detailed look at all the features.
+Now lets move on to see some common use cases. For details, please refer the API Documentation.
 
 
 =========================================================================
 Accessing Rows
 =========================================================================
 
-You can access a row using it's index. It works just like a python
-list. It returns a **RowData** object.
+You can access a row using it's index or it's header. It returns a **BTRowData** object.
 
 .. code:: python
 
-   >>> print(list(table[3]))
+   >>> print(list(table.rows[3]))
    ['Sophia', 2, 'girl', '2010']
 
-To access a particular field of a row, you can use the index, or the header.
+To access a particular field of a row, you can again use the index, or the header
+of the required column.
 
 .. code:: python
 
-   >>> print(table[3][2])
+   >>> print(table.rows[3][2])
    girl
-   >>> print(table[3]['gender'])
+   >>> print(table.rows[3]['gender'])
    girl
 
 
@@ -105,66 +125,67 @@ To access a particular field of a row, you can use the index, or the header.
 Accessing Columns
 =========================================================================
 
-Columns can be accessed using their header names or their index.
-But since name of headers can be duplicated, There are methods
-provided to access columns using their index. If columns are accessed
-using their names, and if more than one column exists with that name
-as it's header, then the first column found would be returned.
-
-It should be noted here that the returned column is not a list. It is
-an iterator.
+You can access a column using it's index or it's header. It returns a **BTColumnData** object.
 
 .. code:: python
 
-   >>> print(list(table['name']))
+   >>> print(list(table.columns['name']))
    ['Jacob', 'Isabella', 'Ethan', 'Sophia', 'Michael']
 
+To access a particular field of a column, you can again use the index, or the header
+of the required row.
+
+.. code:: python
+
+   >>> print(table.columns[2][3])
+   girl
+   >>> print(table.columns[2]['S4'])
+   girl
+
 =========================================================================
 Counting Rows and Columns
 =========================================================================
 
-You can get the number of columns in the table by accessing the
-:attr:`~.BeautifulTable.column_count` property.
+You can get the number of columns or rows in the table by using the
+``len`` function. You can also use the :attr:`~.BeautifulTable.shape`
+attribute.
 
 .. code:: python
 
-   >>> print(table.column_count)
+   >>> print(len(table.columns))
    3
-
-To get the number of rows, you can just use the ``len`` function.
-
-.. code:: python
-
-   >>> print(len(table))
+   >>> print(len(table.rows))
    5
+   >>> print(table.shape)
+   (5,3)
 
 =========================================================================
 Inserting Rows and Columns
 =========================================================================
 
-BeautifulTable provides 2 methods, :meth:`~.BeautifulTable.insert_row` and
-:meth:`~.BeautifulTable.insert_column` for this purpose.
+BeautifulTable provides 2 methods, :meth:`~.BTRowCollection.insert` and
+:meth:`~.BTColumnCollection.insert` for this purpose.
 
 .. code:: python
 
-   >>> table.insert_row(3, ['Gary', 2, 'boy', 2009])
-   >>> table.insert_column(2, 'marks', [78, 67, 82, 56, 86, 74])
+   >>> table.rows.insert(3, ['Gary', 2, 'boy', 2009], header='S6')
+   >>> table.columns.insert(2, [78, 67, 82, 56, 86, 74], header='marks')
    >>> print(table)
-   +----------+------+-------+--------+------+
-   |   name   | rank | marks | gender | year |
-   +----------+------+-------+--------+------+
-   |  Jacob   |  1   |  78   |  boy   | 2010 |
-   +----------+------+-------+--------+------+
-   | Isabella |  1   |  67   |  girl  | 2012 |
-   +----------+------+-------+--------+------+
-   |  Ethan   |  2   |  82   |  boy   | 2008 |
-   +----------+------+-------+--------+------+
-   |   Gary   |  2   |  56   |  boy   | 2009 |
-   +----------+------+-------+--------+------+
-   |  Sophia  |  2   |  86   |  girl  | 2010 |
-   +----------+------+-------+--------+------+
-   | Michael  |  3   |  74   |  boy   | 2011 |
-   +----------+------+-------+--------+------+
+   +----+----------+------+-------+--------+------+
+   |    |   name   | rank | marks | gender | year |
+   +----+----------+------+-------+--------+------+
+   | S1 |  Jacob   |  1   |  78   |  boy   | 2010 |
+   +----+----------+------+-------+--------+------+
+   | S2 | Isabella |  1   |  67   |  girl  | 2012 |
+   +----+----------+------+-------+--------+------+
+   | S3 |  Ethan   |  2   |  82   |  boy   | 2008 |
+   +----+----------+------+-------+--------+------+
+   | S6 |   Gary   |  2   |  56   |  boy   | 2009 |
+   +----+----------+------+-------+--------+------+
+   | S4 |  Sophia  |  2   |  86   |  girl  | 2010 |
+   +----+----------+------+-------+--------+------+
+   | S5 | Michael  |  3   |  74   |  boy   | 2011 |
+   +----+----------+------+-------+--------+------+
 
 
 =========================================================================
@@ -176,38 +197,36 @@ statement.
 
 .. code:: python
 
-   >>> del table[3]
-   >>> del table['year']
+   >>> del table.rows[3]
+   >>> del table.columns['year']
    >>> print(table)
-   +----------+------+-------+--------+
-   |   name   | rank | marks | gender |
-   +----------+------+-------+--------+
-   |  Jacob   |  1   |  78   |  boy   |
-   +----------+------+-------+--------+
-   | Isabella |  1   |  67   |  girl  |
-   +----------+------+-------+--------+
-   |  Ethan   |  2   |  82   |  boy   |
-   +----------+------+-------+--------+
-   |  Sophia  |  2   |  86   |  girl  |
-   +----------+------+-------+--------+
-   | Michael  |  3   |  74   |  boy   |
-   +----------+------+-------+--------+
-
-You can also use the helper methods :meth:`~.BeautifulTable.pop_row`,
-:meth:`~.BeautifulTable.pop_column` to do the same thing. Both these
-methods take the index of the row, or column to be removed.
-
-Instead of the index, you can also pass the header of the column to
-:meth:`~.BeautifulTable.pop_column`. Therefore the following 2
-snippets are equivalent.
+   +----+----------+------+-------+--------+
+   |    |   name   | rank | marks | gender |
+   +----+----------+------+-------+--------+
+   | S1 |  Jacob   |  1   |  78   |  boy   |
+   +----+----------+------+-------+--------+
+   | S2 | Isabella |  1   |  67   |  girl  |
+   +----+----------+------+-------+--------+
+   | S3 |  Ethan   |  2   |  82   |  boy   |
+   +----+----------+------+-------+--------+
+   | S4 |  Sophia  |  2   |  86   |  girl  |
+   +----+----------+------+-------+--------+
+   | S5 | Michael  |  3   |  74   |  boy   |
+   +----+----------+------+-------+--------+
+
+You can also use the helper methods :meth:`~.BTRowCollection.pop`,
+:meth:`~.BTColumnCollection.pop` to do the same thing. Both these
+methods take the index or header of the row/column to be removed.
+
+Therefore the following 2 snippets are equivalent.
 
 .. code:: python
 
-   >>> table.pop_column('marks')
+   >>> table.columns.pop('marks')
 
 .. code:: python
 
-   >>> table.pop_column(2)
+   >>> table.columns.pop(2)
 
 
 =========================================================================
@@ -218,15 +237,15 @@ Let's change the name in the 4th row to ``'Sophie'``.
 
 .. code:: python
 
-   >>> table[3][0] = 'Sophie' # index of 4th row is 3
-   >>> print(table[3])
+   >>> table.rows[3][0] = 'Sophie' # index of 4th row is 3
+   >>> print(list(table.rows[3]))
    ['Sophie', 2, 86, 'girl']
 
 You could have done the same thing using the header.
 
 .. code:: python
 
-   >>> table[3]['name'] = 'Sophie'
+   >>> table.rows[3]['name'] = 'Sophie'
 
 
 Or, you can also change the entire row, or even multiple rows
@@ -234,82 +253,94 @@ using slicing.
 
 .. code:: python
 
-   >>> table[3] = ['Sophie', 2, 56, 'girl']
+   >>> table.rows[3] = ['Sophie', 2, 56, 'girl']
 
 
 You can also update existing columns as shown below.
 
 .. code:: python
 
-   >>> table['marks'] = [75, 46, 89, 56, 82]
+   >>> table.columns['marks'] = [75, 46, 89, 56, 82]
    >>> print(table)
-   +----------+------+-------+--------+
-   |   name   | rank | marks | gender |
-   +----------+------+-------+--------+
-   |  Jacob   |  1   |  75   |  boy   |
-   +----------+------+-------+--------+
-   | Isabella |  1   |  46   |  girl  |
-   +----------+------+-------+--------+
-   |  Ethan   |  2   |  89   |  boy   |
-   +----------+------+-------+--------+
-   |  Sophie  |  2   |  56   |  girl  |
-   +----------+------+-------+--------+
-   | Michael  |  3   |  82   |  boy   |
-   +----------+------+-------+--------+
-
-The methods :meth:`~.BeautifulTable.update_row` and
-:meth:`~.BeautifulTable.update_column` can be used to perform the operations
-discussed in this section.
+   +----+----------+------+-------+--------+
+   |    |   name   | rank | marks | gender |
+   +----+----------+------+-------+--------+
+   | S1 |  Jacob   |  1   |  75   |  boy   |
+   +----+----------+------+-------+--------+
+   | S2 | Isabella |  1   |  46   |  girl  |
+   +----+----------+------+-------+--------+
+   | S3 |  Ethan   |  2   |  89   |  boy   |
+   +----+----------+------+-------+--------+
+   | S4 |  Sophie  |  2   |  56   |  girl  |
+   +----+----------+------+-------+--------+
+   | S5 | Michael  |  3   |  82   |  boy   |
+   +----+----------+------+-------+--------+
+
+The methods :meth:`~.BTRowCollection.update` and :meth:`~.BTColumnCollection.update`
+can be used to perform the operations discussed in this section.
 
 Note that you can only update existing columns but can't create
 a new column using this method. For that you need to use the
-methods :meth:`~.BeautifulTable.append_column` or
-:meth:`~.BeautifulTable.insert_column`.
+methods :meth:`~.BTRowCollection.append`, :meth:`~.BTRowCollection.insert`,
+:meth:`~.BTColumnCollection.append` or :meth:`~.BTColumnCollection.insert`.
 
 
 =========================================================================
 Searching for rows or columns headers
 =========================================================================
 
-Cheking if a header is in the table.
+Cheking if a column header is in the table.
+
+.. code:: python
+
+   >>> 'rank' in table.columns.header
+   True
+
+Cheking if a row header is in the table.
 
 .. code:: python
 
-   >>> 'rank' in table
+   >>> 'S2' in table.rows.header
    True
 
 Cheking if a row is in table
 
 .. code:: python
 
-   >>> ["Ethan", 2, 89, "boy"] in table
+   >>> ["Ethan", 2, 89, "boy"] in table.rows
    True
 
+Cheking if a column is in table
+
+.. code:: python
+
+   >>> ["Jacob", "Isabella", "Ethan", "Sophie", "Michael"] in table.columns
+   True
 
 =========================================================================
 Sorting based on a Column
 =========================================================================
 
-You can also :meth:`~.BeautifulTable.sort` the table based on a column by
-specifying it's index or it's header.
+You can also :meth:`~.:meth:`~.BTRowCollection.sort` the table based on a column
+by specifying it's index or it's header.
 
 .. code:: python
 
-   >>> table.sort('name')
+   >>> table.rows.sort('marks')
    >>> print(table)
-   +----------+------+-------+--------+
-   |   name   | rank | marks | gender |
-   +----------+------+-------+--------+
-   |  Ethan   |  2   |  89   |  boy   |
-   +----------+------+-------+--------+
-   | Isabella |  1   |  46   |  girl  |
-   +----------+------+-------+--------+
-   |  Jacob   |  1   |  75   |  boy   |
-   +----------+------+-------+--------+
-   | Michael  |  3   |  82   |  boy   |
-   +----------+------+-------+--------+
-   |  Sophie  |  2   |  56   |  girl  |
-   +----------+------+-------+--------+
+   +----+----------+------+-------+--------+
+   |    |   name   | rank | marks | gender |
+   +----+----------+------+-------+--------+
+   | S2 | Isabella |  1   |  46   |  girl  |
+   +----+----------+------+-------+--------+
+   | S4 |  Sophia  |  2   |  56   |  girl  |
+   +----+----------+------+-------+--------+
+   | S1 |  Jacob   |  1   |  75   |  boy   |
+   +----+----------+------+-------+--------+
+   | S5 | Michael  |  3   |  82   |  boy   |
+   +----+----------+------+-------+--------+
+   | S3 |  Ethan   |  2   |  89   |  boy   |
+   +----+----------+------+-------+--------+
 
 =========================================================================
 Customizing the look of the Table
@@ -323,22 +354,63 @@ Let's change the way some columns are aligned in our table.
 
 .. code:: python
 
-   >>> table.column_alignments['name'] = BeautifulTable.ALIGN_LEFT
-   >>> table.column_alignments['gender'] = BeautifulTable.ALIGN_RIGHT
+   >>> table.columns.alignment['name'] = BeautifulTable.ALIGN_LEFT
+   >>> table.columns.alignment['gender'] = BeautifulTable.ALIGN_RIGHT
    >>> print(table)
-   +----------+------+--------+------+
-   | name     | rank | gender | year |
-   +----------+------+--------+------+
-   | Jacob    |  1   |    boy | 2010 |
-   +----------+------+--------+------+
-   | Isabella |  1   |   girl | 2012 |
-   +----------+------+--------+------+
-   | Ethan    |  2   |    boy | 2008 |
-   +----------+------+--------+------+
-   | Sophia   |  2   |   girl | 2010 |
-   +----------+------+--------+------+
-   | Michael  |  3   |    boy | 2011 |
-   +----------+------+--------+------+
+   +----+----------+------+-------+--------+
+   |    | name     | rank | marks | gender |
+   +----+----------+------+-------+--------+
+   | S2 | Isabella |  1   |  46   |   girl |
+   +----+----------+------+-------+--------+
+   | S4 | Sophia   |  2   |  56   |   girl |
+   +----+----------+------+-------+--------+
+   | S1 | Jacob    |  1   |  75   |    boy |
+   +----+----------+------+-------+--------+
+   | S5 | Michael  |  3   |  82   |    boy |
+   +----+----------+------+-------+--------+
+   | S3 | Ethan    |  2   |  89   |    boy |
+   +----+----------+------+-------+--------+
+
+You can also set all columns to a specific alignment
+
+.. code:: python
+
+   >>> table.columns.alignment = BeautifulTable.ALIGN_RIGHT
+   >>> print(table)
+   +----+----------+------+-------+--------+
+   |    |     name | rank | marks | gender |
+   +----+----------+------+-------+--------+
+   | S2 | Isabella |    1 |    46 |   girl |
+   +----+----------+------+-------+--------+
+   | S4 |   Sophia |    2 |    56 |   girl |
+   +----+----------+------+-------+--------+
+   | S1 |    Jacob |    1 |    75 |    boy |
+   +----+----------+------+-------+--------+
+   | S5 |  Michael |    3 |    82 |    boy |
+   +----+----------+------+-------+--------+
+   | S3 |    Ethan |    2 |    89 |    boy |
+   +----+----------+------+-------+--------+
+
+Headers can have a different alignment that the column.
+
+.. code:: python
+
+   >>> table.columns.header.alignment= BeautifulTable.ALIGN_RIGHT
+   >>> table.columns.alignment = BeautifulTable.ALIGN_LEFT
+   >>> print(table)
+   +----+----------+------+-------+--------+
+   |    |     name | rank | marks | gender |
+   +----+----------+------+-------+--------+
+   | S2 | Isabella | 1    | 46    | girl   |
+   +----+----------+------+-------+--------+
+   | S4 | Sophia   | 2    | 56    | girl   |
+   +----+----------+------+-------+--------+
+   | S1 | Jacob    | 1    | 75    | boy    |
+   +----+----------+------+-------+--------+
+   | S5 | Michael  | 3    | 82    | boy    |
+   +----+----------+------+-------+--------+
+   | S3 | Ethan    | 2    | 89    | boy    |
+   +----+----------+------+-------+--------+
 
 
 -------------------------------------------------------------------------
@@ -350,25 +422,25 @@ the alignment.
 
 .. code:: python
 
-   >>> table.left_padding_widths['rank'] = 5
-   >>> table.right_padding_widths['rank'] = 3
+   >>> table.columns.padding_left['rank'] = 5
+   >>> table.columns.padding_right['rank'] = 3
    >>> print(table)
-   +----------+------------+--------+------+
-   | name     |     rank   | gender | year |
-   +----------+------------+--------+------+
-   | Jacob    |      1     |    boy | 2010 |
-   +----------+------------+--------+------+
-   | Isabella |      1     |   girl | 2012 |
-   +----------+------------+--------+------+
-   | Ethan    |      2     |    boy | 2008 |
-   +----------+------------+--------+------+
-   | Sophia   |      2     |   girl | 2010 |
-   +----------+------------+--------+------+
-   | Michael  |      3     |    boy | 2011 |
-   +----------+------------+--------+------+
-
-
-You can use a helper method :meth:`~.BeautifulTable.set_padding_widths` to
+   +----+----------+------------+--------+
+   |    |   name   |     rank   | gender |
+   +----+----------+------------+--------+
+   | S1 |  Jacob   |      1     |  boy   |
+   +----+----------+------------+--------+
+   | S2 | Isabella |      1     |  girl  |
+   +----+----------+------------+--------+
+   | S3 |  Ethan   |      2     |  boy   |
+   +----+----------+------------+--------+
+   | S4 |  Sophia  |      2     |  girl  |
+   +----+----------+------------+--------+
+   | S5 | Michael  |      3     |  boy   |
+   +----+----------+------------+--------+
+
+
+You can use a helper attribute :attr:`~.BTColumnCollection.padding` to
 set the left and right padding to a common value.
 
 
@@ -386,19 +458,19 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_DEFAULT)
      >>> print(table)
-     +----------+------+--------+
-     |   name   | rank | gender |
-     +----------+------+--------+
-     |  Jacob   |  1   |  boy   |
-     +----------+------+--------+
-     | Isabella |  1   |  girl  |
-     +----------+------+--------+
-     |  Ethan   |  2   |  boy   |
-     +----------+------+--------+
-     |  Sophia  |  2   |  girl  |
-     +----------+------+--------+
-     | Michael  |  3   |  boy   |
-     +----------+------+--------+
+     +----+----------+------+--------+
+     |    |   name   | rank | gender |
+     +----+----------+------+--------+
+     | S1 |  Jacob   |  1   |  boy   |
+     +----+----------+------+--------+
+     | S2 | Isabella |  1   |  girl  |
+     +----+----------+------+--------+
+     | S3 |  Ethan   |  2   |  boy   |
+     +----+----------+------+--------+
+     | S4 |  Sophia  |  2   |  girl  |
+     +----+----------+------+--------+
+     | S5 | Michael  |  3   |  boy   |
+     +----+----------+------+--------+
 
 * **STYLE_NONE**
 
@@ -406,12 +478,12 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_NONE)
      >>> print(table)
-        name    rank  gender
-       Jacob     1     boy
-      Isabella   1     girl
-       Ethan     2     boy
-       Sophia    2     girl
-      Michael    3     boy
+           name    rank  gender
+     S1   Jacob     1     boy
+     S2  Isabella   1     girl
+     S3   Ethan     2     boy
+     S4   Sophia    2     girl
+     S5  Michael    3     boy
 
 * **STYLE_DOTTED**
 
@@ -419,15 +491,15 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_DOTTED)
      >>> print(table)
-     ............................
-     :   name   : rank : gender :
-     ............................
-     :  Jacob   :  1   :  boy   :
-     : Isabella :  1   :  girl  :
-     :  Ethan   :  2   :  boy   :
-     :  Sophia  :  2   :  girl  :
-     : Michael  :  3   :  boy   :
-     ............................
+     .................................
+     :    :   name   : rank : gender :
+     .................................
+     : S1 :  Jacob   :  1   :  boy   :
+     : S2 : Isabella :  1   :  girl  :
+     : S3 :  Ethan   :  2   :  boy   :
+     : S4 :  Sophia  :  2   :  girl  :
+     : S5 : Michael  :  3   :  boy   :
+     .................................
 
 * **STYLE_SEPARATED**
 
@@ -435,19 +507,19 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_SEPARATED)
      >>> print(table)
-     +==========+======+========+
-     |   name   | rank | gender |
-     +==========+======+========+
-     |  Jacob   |  1   |  boy   |
-     +----------+------+--------+
-     | Isabella |  1   |  girl  |
-     +----------+------+--------+
-     |  Ethan   |  2   |  boy   |
-     +----------+------+--------+
-     |  Sophia  |  2   |  girl  |
-     +----------+------+--------+
-     | Michael  |  3   |  boy   |
-     +----------+------+--------+
+     +====+==========+======+========+
+     |    |   name   | rank | gender |
+     +====+==========+======+========+
+     | S1 |  Jacob   |  1   |  boy   |
+     +----+----------+------+--------+
+     | S2 | Isabella |  1   |  girl  |
+     +----+----------+------+--------+
+     | S3 |  Ethan   |  2   |  boy   |
+     +----+----------+------+--------+
+     | S4 |  Sophia  |  2   |  girl  |
+     +----+----------+------+--------+
+     | S5 | Michael  |  3   |  boy   |
+     +----+----------+------+--------+
 
 * **STYLE_COMPACT**
 
@@ -455,13 +527,13 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_COMPACT)
      >>> print(table)
-        name     rank   gender
-     ---------- ------ --------
-       Jacob      1      boy
-      Isabella    1      girl
-       Ethan      2      boy
-       Sophia     2      girl
-      Michael     3      boy
+             name     rank   gender
+     ---- ---------- ------ --------
+     S1    Jacob      1      boy
+     S2   Isabella    1      girl
+     S3    Ethan      2      boy
+     S4    Sophia     2      girl
+     S5   Michael     3      boy
 
 * **STYLE_MYSQL**
 
@@ -469,19 +541,19 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_MYSQL)
      >>> print(table)  # Yes, the default style is same as this style
-     +----------+------+--------+
-     |   name   | rank | gender |
-     +----------+------+--------+
-     |  Jacob   |  1   |  boy   |
-     +----------+------+--------+
-     | Isabella |  1   |  girl  |
-     +----------+------+--------+
-     |  Ethan   |  2   |  boy   |
-     +----------+------+--------+
-     |  Sophia  |  2   |  girl  |
-     +----------+------+--------+
-     | Michael  |  3   |  boy   |
-     +----------+------+--------+
+     +----+----------+------+--------+
+     |    |   name   | rank | gender |
+     +----+----------+------+--------+
+     | S1 |  Jacob   |  1   |  boy   |
+     +----+----------+------+--------+
+     | S2 | Isabella |  1   |  girl  |
+     +----+----------+------+--------+
+     | S3 |  Ethan   |  2   |  boy   |
+     +----+----------+------+--------+
+     | S4 |  Sophia  |  2   |  girl  |
+     +----+----------+------+--------+
+     | S5 | Michael  |  3   |  boy   |
+     +----+----------+------+--------+
 
 * **STYLE_MARKDOWN**
 
@@ -489,13 +561,13 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_MARKDOWN)
      >>> print(table)  # Markdown alignment not supported currently
-     |   name   | rank | gender |
-     |----------|------|--------|
-     |  Jacob   |  1   |  boy   |
-     | Isabella |  1   |  girl  |
-     |  Ethan   |  2   |  boy   |
-     |  Sophia  |  2   |  girl  |
-     | Michael  |  3   |  boy   |
+     |    |   name   | rank | gender |
+     |----|----------|------|--------|
+     | S1 |  Jacob   |  1   |  boy   |
+     | S2 | Isabella |  1   |  girl  |
+     | S3 |  Ethan   |  2   |  boy   |
+     | S4 |  Sophia  |  2   |  girl  |
+     | S5 | Michael  |  3   |  boy   |
 
 * **STYLE_RST**
 
@@ -503,15 +575,15 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_RST)
      >>> print(table)
-     ========== ====== ========
-        name     rank   gender
-     ========== ====== ========
-       Jacob      1      boy
-      Isabella    1      girl
-       Ethan      2      boy
-       Sophia     2      girl
-      Michael     3      boy
-     ========== ====== ========
+     ==== ========== ====== ========
+             name     rank   gender
+     ==== ========== ====== ========
+     S1    Jacob      1      boy
+     S2   Isabella    1      girl
+     S3    Ethan      2      boy
+     S4    Sophia     2      girl
+     S5   Michael     3      boy
+     ==== ========== ====== ========
 
 * **STYLE_BOX**
 
@@ -519,19 +591,19 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_BOX)
      >>> print(table)
-     ┌──────────┬──────┬────────┐
-     │   name   │ rank │ gender │
-     ├──────────┼──────┼────────┤
-     │  Jacob   │  1   │  boy   │
-     ├──────────┼──────┼────────┤
-     │ Isabella │  1   │  girl  │
-     ├──────────┼──────┼────────┤
-     │  Ethan   │  2   │  boy   │
-     ├──────────┼──────┼────────┤
-     │  Sophia  │  2   │  girl  │
-     ├──────────┼──────┼────────┤
-     │ Michael  │  3   │  boy   │
-     └──────────┴──────┴────────┘
+     ┌────┬──────────┬──────┬────────┐
+     │    │   name   │ rank │ gender │
+     ├────┼──────────┼──────┼────────┤
+     │ S1 │  Jacob   │  1   │  boy   │
+     ├────┼──────────┼──────┼────────┤
+     │ S2 │ Isabella │  1   │  girl  │
+     ├────┼──────────┼──────┼────────┤
+     │ S3 │  Ethan   │  2   │  boy   │
+     ├────┼──────────┼──────┼────────┤
+     │ S4 │  Sophia  │  2   │  girl  │
+     ├────┼──────────┼──────┼────────┤
+     │ S5 │ Michael  │  3   │  boy   │
+     └────┴──────────┴──────┴────────┘
 
 * **STYLE_BOX_DOUBLED**
 
@@ -539,19 +611,19 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_BOX_DOUBLED)
      >>> print(table)
-     ╔══════════╦══════╦════════╗
-     ║   name   ║ rank ║ gender ║
-     ╠══════════╬══════╬════════╣
-     ║  Jacob   ║  1   ║  boy   ║
-     ╠══════════╬══════╬════════╣
-     ║ Isabella ║  1   ║  girl  ║
-     ╠══════════╬══════╬════════╣
-     ║  Ethan   ║  2   ║  boy   ║
-     ╠══════════╬══════╬════════╣
-     ║  Sophia  ║  2   ║  girl  ║
-     ╠══════════╬══════╬════════╣
-     ║ Michael  ║  3   ║  boy   ║
-     ╚══════════╩══════╩════════╝
+     ╔════╦══════════╦══════╦════════╗
+     ║    ║   name   ║ rank ║ gender ║
+     ╠════╬══════════╬══════╬════════╣
+     ║ S1 ║  Jacob   ║  1   ║  boy   ║
+     ╠════╬══════════╬══════╬════════╣
+     ║ S2 ║ Isabella ║  1   ║  girl  ║
+     ╠════╬══════════╬══════╬════════╣
+     ║ S3 ║  Ethan   ║  2   ║  boy   ║
+     ╠════╬══════════╬══════╬════════╣
+     ║ S4 ║  Sophia  ║  2   ║  girl  ║
+     ╠════╬══════════╬══════╬════════╣
+     ║ S5 ║ Michael  ║  3   ║  boy   ║
+     ╚════╩══════════╩══════╩════════╝
 
 * **STYLE_BOX_ROUNDED**
 
@@ -559,19 +631,19 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_BOX_ROUNDED)
      >>> print(table)
-     ╭──────────┬──────┬────────╮
-     │   name   │ rank │ gender │
-     ├──────────┼──────┼────────┤
-     │  Jacob   │  1   │  boy   │
-     ├──────────┼──────┼────────┤
-     │ Isabella │  1   │  girl  │
-     ├──────────┼──────┼────────┤
-     │  Ethan   │  2   │  boy   │
-     ├──────────┼──────┼────────┤
-     │  Sophia  │  2   │  girl  │
-     ├──────────┼──────┼────────┤
-     │ Michael  │  3   │  boy   │
-     ╰──────────┴──────┴────────╯
+     ╭────┬──────────┬──────┬────────╮
+     │    │   name   │ rank │ gender │
+     ├────┼──────────┼──────┼────────┤
+     │ S1 │  Jacob   │  1   │  boy   │
+     ├────┼──────────┼──────┼────────┤
+     │ S2 │ Isabella │  1   │  girl  │
+     ├────┼──────────┼──────┼────────┤
+     │ S3 │  Ethan   │  2   │  boy   │
+     ├────┼──────────┼──────┼────────┤
+     │ S4 │  Sophia  │  2   │  girl  │
+     ├────┼──────────┼──────┼────────┤
+     │ S5 │ Michael  │  3   │  boy   │
+     ╰────┴──────────┴──────┴────────╯
 
 * **STYLE_GRID**
 
@@ -579,19 +651,19 @@ of the table. The following styles are available:
 
      >>> table.set_style(BeautifulTable.STYLE_GRID)
      >>> print(table)
-     ╔══════════╤══════╤════════╗
-     ║   name   │ rank │ gender ║
-     ╟──────────┼──────┼────────╢
-     ║  Jacob   │  1   │  boy   ║
-     ╟──────────┼──────┼────────╢
-     ║ Isabella │  1   │  girl  ║
-     ╟──────────┼──────┼────────╢
-     ║  Ethan   │  2   │  boy   ║
-     ╟──────────┼──────┼────────╢
-     ║  Sophia  │  2   │  girl  ║
-     ╟──────────┼──────┼────────╢
-     ║ Michael  │  3   │  boy   ║
-     ╚══════════╧══════╧════════╝
+     ╔════╤══════════╤══════╤════════╗
+     ║    │   name   │ rank │ gender ║
+     ╟────┼──────────┼──────┼────────╢
+     ║ S1 │  Jacob   │  1   │  boy   ║
+     ╟────┼──────────┼──────┼────────╢
+     ║ S2 │ Isabella │  1   │  girl  ║
+     ╟────┼──────────┼──────┼────────╢
+     ║ S3 │  Ethan   │  2   │  boy   ║
+     ╟────┼──────────┼──────┼────────╢
+     ║ S4 │  Sophia  │  2   │  girl  ║
+     ╟────┼──────────┼──────┼────────╢
+     ║ S5 │ Michael  │  3   │  boy   ║
+     ╚════╧══════════╧══════╧════════╝
 
 For more finer customization, you can change what characters are used to draw
 various parts of the table. Here we show you an example of how you can use
@@ -608,19 +680,19 @@ this feature. You can read the API Reference for more details.
    >>> table.column_separator_char = ':'
    >>> table.row_separator_char = '~'
    >>> print(table)
-   <~><~><~><~><~><~><~><~><~><
-   o   name   : rank : gender o
-   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-   o  Jacob   :  1   :  boy   o
-   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-   o Isabella :  1   :  girl  o
-   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-   o  Ethan   :  2   :  boy   o
-   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-   o  Sophia  :  2   :  girl  o
-   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-   o Michael  :  3   :  boy   o
-   ============================
+   <~><~><~><~><~><~><~><~><~><~><~>
+   o    :   name   : rank : gender o
+   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+   o S1 :  Jacob   :  1   :  boy   o
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+   o S2 : Isabella :  1   :  girl  o
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+   o S3 :  Ethan   :  2   :  boy   o
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+   o S4 :  Sophia  :  2   :  girl  o
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+   o S5 : Michael  :  3   :  boy   o
+   =================================
 
 As you can see, you can change quite a lot about your *BeautifulTable* instance.
 For further sections, We switch the look of the table to *default* again.
@@ -633,29 +705,37 @@ Colored Tables
 ansi escape sequences. You can also use any library which makes use of
 these sequences to produce colored text output.
 
+::
+
+    python3 -m pip install termcolor
+
+.. code:: python
+
+   >>> from termcolor import colored
+
 .. code:: python
 
-   >>> table.append_row([colored("John", 'red'), 4, colored("boy", 'blue')])
+   >>> table.rows.append([colored("John", 'red'), 4, colored("boy", 'blue')])
    >>> print(table)
 
 .. raw:: html
 
    <p style="font-family: monospace; background-color: #eeffcc;">
-   +----------+------+--------+<br />
-   |&nbsp;&nbsp; name&nbsp;&nbsp; | rank | gender |<br />
-   +----------+------+--------+<br />
-   |&nbsp; Jacob&nbsp;&nbsp; |&nbsp; 1 &nbsp; |&nbsp; boy&nbsp;&nbsp; |<br />
-   +----------+------+--------+<br />
-   | Isabella |&nbsp; 1&nbsp;&nbsp; |&nbsp; girl&nbsp; |<br />
-   +----------+------+--------+<br />
-   |&nbsp; Ethan&nbsp;&nbsp; |&nbsp; 2&nbsp;&nbsp; |&nbsp; boy&nbsp;&nbsp; |<br />
-   +----------+------+--------+<br />
-   |&nbsp; Sophia&nbsp; |&nbsp; 2&nbsp;&nbsp; |&nbsp; girl&nbsp; |<br />
-   +----------+------+--------+<br />
-   | Michael&nbsp; |&nbsp; 3&nbsp;&nbsp; |&nbsp; boy&nbsp;&nbsp; |<br />
-   +----------+------+--------+<br />
-   |&nbsp;&nbsp; <span style="color: #ff0000;">John</span>&nbsp;&nbsp; |&nbsp; 4&nbsp;&nbsp; |&nbsp; <span style="color: #0000ff;">boy</span>&nbsp;&nbsp; |<br />
-   +----------+------+--------+
+   +----+----------+------+--------+<br />
+   |&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; name&nbsp;&nbsp; | rank | gender |<br />
+   +----+----------+------+--------+<br />
+   | S1 |&nbsp; Jacob&nbsp;&nbsp; |&nbsp; 1 &nbsp; |&nbsp; boy&nbsp;&nbsp; |<br />
+   +----+----------+------+--------+<br />
+   | S2 | Isabella |&nbsp; 1&nbsp;&nbsp; |&nbsp; girl&nbsp; |<br />
+   +----+----------+------+--------+<br />
+   | S3 |&nbsp; Ethan&nbsp;&nbsp; |&nbsp; 2&nbsp;&nbsp; |&nbsp; boy&nbsp;&nbsp; |<br />
+   +----+----------+------+--------+<br />
+   | S4 |&nbsp; Sophia&nbsp; |&nbsp; 2&nbsp;&nbsp; |&nbsp; girl&nbsp; |<br />
+   +----+----------+------+--------+<br />
+   | S5 | Michael&nbsp; |&nbsp; 3&nbsp;&nbsp; |&nbsp; boy&nbsp;&nbsp; |<br />
+   +----+----------+------+--------+<br />
+   | S6 |&nbsp;&nbsp; <span style="color: #ff0000;">John</span>&nbsp;&nbsp; |&nbsp; 4&nbsp;&nbsp; |&nbsp; <span style="color: #0000ff;">boy</span>&nbsp;&nbsp; |<br />
+   +----+----------+------+--------+
    </p>
 
 You can also use these sequences for making texts bold, italics, etc.
@@ -670,10 +750,10 @@ a new line. **beautifultable** parses ``\n`` as a paragraph change.
 .. code:: python
 
    >>> new_table = BeautifulTable(max_width=40)
-   >>> new_table.column_headers = ["Heading 1", "Heading 2"]
-   >>> new_table.append_row(["first Line\nsecond Line", "single line"])
-   >>> new_table.append_row(["first Line\nsecond Line\nthird Line", "first Line\nsecond Line"])
-   >>> new_table.append_row(["single line", "this is a very long first line\nThis is a very long second line"])
+   >>> new_table.columns.header = ["Heading 1", "Heading 2"]
+   >>> new_table.rows.append(["first Line\nsecond Line", "single line"])
+   >>> new_table.rows.append(["first Line\nsecond Line\nthird Line", "first Line\nsecond Line"])
+   >>> new_table.rows.append(["single line", "this is a very long first line\nThis is a very long second line"])
    >>> print(new_table)
    +-------------+------------------------+
    |  Heading 1  |       Heading 2        |
@@ -702,40 +782,36 @@ works.
 .. code:: python
 
    >>> subtable = BeautifulTable()
-   >>> subtable.column_headers = ["name", "rank", "gender"]
-   >>> subtable.append_row(["Jacob", 1, "boy"])
-   >>> subtable.append_row(["Isabella", 1, "girl"])
+   >>> subtable.rows.append(["Jacob", 1, "boy"])
+   >>> subtable.rows.append(["Isabella", 1, "girl"])
+   >>> subtable.left_border_char = ''
+   >>> subtable.right_border_char = ''
+   >>> subtable.top_border_char = ''
+   >>> subtable.bottom_border_char = ''
    >>> parent_table = BeautifulTable()
-   >>> parent_table.column_headers = ["Heading 1", "Heading 2"]
-   >>> parent_table.append_row(["Sample text", "Another sample text"])
-   >>> parent_table.append_row([subtable, "More sample text"])
+   >>> parent_table.columns.header = ["Heading 1", "Heading 2"]
+   >>> parent_table.rows.append(["Sample text", "Another sample text"])
+   >>> parent_table.rows.append([subtable, "More sample text"])
+   >>> parent_table.columns.padding_left[0] = 0
+   >>> parent_table.columns.padding_right[0] = 0
    >>> print(parent_table)
-   +------------------------------+---------------------+
-   |          Heading 1           |      Heading 2      |
-   +------------------------------+---------------------+
-   |         Sample text          | Another sample text |
-   +------------------------------+---------------------+
-   | +----------+------+--------+ |  More sample text   |
-   | |   name   | rank | gender | |                     |
-   | +----------+------+--------+ |                     |
-   | |  Jacob   |  1   |  boy   | |                     |
-   | +----------+------+--------+ |                     |
-   | | Isabella |  1   |  girl  | |                     |
-   | +----------+------+--------+ |                     |
-   +------------------------------+---------------------+
+   +---------------------+---------------------+
+   |      Heading 1      |      Heading 2      |
+   +---------------------+---------------------+
+   |     Sample text     | Another sample text |
+   +---------------------+---------------------+
+   |  Jacob   | 1 | boy  |  More sample text   |
+   |----------+---+------|                     |
+   | Isabella | 1 | girl |                     |
+   +---------------------+---------------------+
 
 
 =========================================================================
 Support for Multibyte Unicode characters
 =========================================================================
 
-**beautifultable** comes with in-built support for multibyte unicode such as
-east-asian characters. To enable support, You need to install the *wcwidth*
-library from PyPI.
-
-::
-
-    pip install wcwidth
+**beautifultable** comes with built-in support for multibyte unicode such as
+east-asian characters.
 
 
 You can do much more with BeautifulTable but this much should give you a
diff --git a/docs/source/beautifultable.rst b/docs/source/beautifultable.rst
index 2733ff5..17f55da 100644
--- a/docs/source/beautifultable.rst
+++ b/docs/source/beautifultable.rst
@@ -5,5 +5,27 @@ Module contents
 ---------------
 
 .. automodule:: beautifultable
-    :members:
+    :imported-members:
     :show-inheritance:
+
+.. autoclass:: beautifultable.BeautifulTable
+    :members:
+    :special-members: __copy__, __deepcopy__
+
+.. autoclass:: beautifultable.BTRowCollection
+    :members:
+    :special-members: __getitem__, __delitem__, __setitem__, __len__, __contains__, __iter__
+
+.. autoclass:: beautifultable.BTColumnCollection
+    :members:
+    :special-members: __getitem__, __delitem__, __setitem__, __len__, __contains__, __iter__
+
+.. autoclass:: beautifultable.BTRowHeader
+    :members:
+    :special-members: __getitem__,  __setitem__
+    :inherited-members:
+
+.. autoclass:: beautifultable.BTColumnHeader
+    :members:
+    :special-members: __getitem__,  __setitem__
+    :inherited-members:
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 9efe5f4..a1dbd96 100644
--- a/setup.py
+++ b/setup.py
@@ -3,7 +3,7 @@ import os
 from setuptools import setup
 from codecs import open
 
-
+install_requires = ["wcwidth"]
 extras_require = {}
 
 this_dir = os.path.abspath(os.path.dirname(__file__))
@@ -31,6 +31,7 @@ setup(
     license=about["__license__"],
     keywords="table terminal ascii",
     extras_require=extras_require,
+    install_requires=install_requires,
     classifiers=[
         "Development Status :: 3 - Alpha",
         "Environment :: Console",
@@ -41,6 +42,7 @@ setup(
         "Programming Language :: Python :: 3.5",
         "Programming Language :: Python :: 3.6",
         "Programming Language :: Python :: 3.7",
+        "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
         "Topic :: Printing",
diff --git a/tox.ini b/tox.ini
index 6982cb0..437f610 100644
--- a/tox.ini
+++ b/tox.ini
@@ -4,13 +4,13 @@
 # and then run "tox" from this directory.
 
 [tox]
-envlist = black, flake8, coverage, py34, py35, py36, py37, pypy3
+envlist = black, flake8, coverage, py34, py35, py36, py37, py38, pypy3
 
 [testenv]
 deps =
     wcwidth
 commands =
-    python test.py
+    python -W error test.py
 
 [testenv:black]
 deps = black
@@ -23,7 +23,7 @@ commands = flake8 beautifultable/ setup.py test.py
 [testenv:coverage]
 passenv = TOXENV CI TRAVIS TRAVIS_*
 deps =
-    wcwidth
+    wcwidth<=0.2.5
     coverage
     codecov
 commands =


Test patch:
diff --git a/test.py b/test.py
index 2e8699f..8211d80 100644
--- a/test.py
+++ b/test.py
@@ -11,14 +11,18 @@ class TableOperationsTestCase(unittest.TestCase):
     def setUp(self):
         self.create_table()
 
-    def create_table(self, max_width=80):
-        table = BeautifulTable(max_width)
-        table.column_headers = ["name", "rank", "gender"]
-        table.append_row(["Jacob", 1, "boy"])
-        table.append_row(["Isabella", 1, "girl"])
-        table.append_row(["Ethan", 2, "boy"])
-        table.append_row(["Sophia", 2, "girl"])
-        table.append_row(["Michael", 3, "boy"])
+    def create_table(self, maxwidth=80):
+        table = BeautifulTable(maxwidth=maxwidth)
+
+        table.rows.append(["Jacob", 1, "boy"])
+        table.rows.append(["Isabella", 1, "girl"])
+        table.rows.append(["Ethan", 2, "boy"])
+        table.rows.append(["Sophia", 2, "girl"])
+        table.rows.append(["Michael", 3, "boy"])
+
+        table.columns.header = ["name", "rank", "gender"]
+        table.rows.header = ["S1", "S2", "S3", "S4", "S5"]
+
         self.table = table
 
     def compare_iterable(self, iterable1, iterable2):
@@ -28,18 +32,18 @@ class TableOperationsTestCase(unittest.TestCase):
     # Test for table operations
 
     def test_filter(self):
-        new_table = self.table.filter(lambda x: x["rank"] > 1)
-        self.assertEqual(len(self.table), 5)
+        new_table = self.table.rows.filter(lambda x: x["rank"] > 1)
+        self.assertEqual(len(self.table.rows), 5)
         rows = [
             ["Ethan", 2, "boy"],
             ["Sophia", 2, "girl"],
             ["Michael", 3, "boy"],
         ]
-        for row_t, row in zip(new_table, rows):
+        for row_t, row in zip(new_table.rows, rows):
             self.compare_iterable(row_t, row)
 
     def test_sort_by_index(self):
-        self.table.sort(0)
+        self.table.rows.sort(0)
         rows = [
             ["Ethan", 2, "boy"],
             ["Isabella", 1, "girl"],
@@ -47,11 +51,11 @@ class TableOperationsTestCase(unittest.TestCase):
             ["Michael", 3, "boy"],
             ["Sophia", 2, "girl"],
         ]
-        for row_t, row in zip(self.table, rows):
+        for row_t, row in zip(self.table.rows, rows):
             self.compare_iterable(row_t, row)
 
     def test_sort_by_index_reversed(self):
-        self.table.sort(0, reverse=True)
+        self.table.rows.sort(0, reverse=True)
         rows = [
             ["Ethan", 2, "boy"],
             ["Isabella", 1, "girl"],
@@ -59,11 +63,11 @@ class TableOperationsTestCase(unittest.TestCase):
             ["Michael", 3, "boy"],
             ["Sophia", 2, "girl"],
         ]
-        for row_t, row in zip(self.table, reversed(rows)):
+        for row_t, row in zip(self.table.rows, reversed(rows)):
             self.compare_iterable(row_t, row)
 
     def test_sort_by_header(self):
-        self.table.sort("name")
+        self.table.rows.sort("name")
         rows = [
             ["Ethan", 2, "boy"],
             ["Isabella", 1, "girl"],
@@ -71,11 +75,11 @@ class TableOperationsTestCase(unittest.TestCase):
             ["Michael", 3, "boy"],
             ["Sophia", 2, "girl"],
         ]
-        for row_t, row in zip(self.table, rows):
+        for row_t, row in zip(self.table.rows, rows):
             self.compare_iterable(row_t, row)
 
     def test_sort_by_callable(self):
-        self.table.sort(lambda x: (x[1], x[0]))
+        self.table.rows.sort(lambda x: (x[1], x[0]))
         rows = [
             ["Isabella", 1, "girl"],
             ["Jacob", 1, "boy"],
@@ -83,381 +87,481 @@ class TableOperationsTestCase(unittest.TestCase):
             ["Sophia", 2, "girl"],
             ["Michael", 3, "boy"],
         ]
-        for row_t, row in zip(self.table, rows):
+        for row_t, row in zip(self.table.rows, rows):
             self.compare_iterable(row_t, row)
 
     def test_sort_raises_exception(self):
         with self.assertRaises(TypeError):
-            self.table.sort(None)
+            self.table.rows.sort(None)
 
     # Tests for column operations
 
     def test_column_count(self):
-        self.assertEqual(self.table.column_count, 3)
+        self.assertEqual(len(self.table.columns), 3)
 
     def test_access_column_by_header(self):
         column = ["Jacob", "Isabella", "Ethan", "Sophia", "Michael"]
-        self.compare_iterable(column, self.table["name"])
+        self.compare_iterable(column, self.table.columns["name"])
         with self.assertRaises(KeyError):
-            self.table["name1"]
+            self.table.columns["name1"]
+
+    def test_access_column_element_by_index(self):
+        self.assertEqual(self.table.columns[0][2], "Ethan")
+
+    def test_access_column_element_by_header(self):
+        self.assertEqual(self.table.columns["name"][2], "Ethan")
 
     def test_get_column_index(self):
-        self.assertEqual(self.table.get_column_index("gender"), 2)
+        self.assertEqual(self.table.columns.header.index("gender"), 2)
         with self.assertRaises(KeyError):
-            self.table.get_column_index("rank1")
+            self.table.columns.header.index("rank1")
 
     def test_get_column_header(self):
-        self.assertEqual(self.table.get_column_header(2), "gender")
+        self.assertEqual(self.table.columns.header[2], "gender")
         with self.assertRaises(IndexError):
-            self.table.get_column_header(self.table.column_count)
+            self.table.columns.header[len(self.table.columns)]
 
     def test_append_column(self):
         title = "year"
         column = ["2010", "2012", "2008", "2010", "2011"]
-        self.table.append_column(title, column)
-        self.assertEqual(self.table.column_count, 4)
-        last_column = self.table.get_column(self.table.column_count - 1)
+        self.table.columns.append(column, title)
+        self.assertEqual(len(self.table.columns), 4)
+        last_column = self.table.columns[len(self.table.columns) - 1]
         self.compare_iterable(column, last_column)
 
     def test_insert_column(self):
         column = ["2010", "2012", "2008", "2010", "2011"]
         title = "year"
         position = 2
-        self.table.insert_column(position, title, column)
-        self.assertEqual(self.table.column_count, 4)
-        self.compare_iterable(column, self.table.get_column(position))
+        self.table.columns.insert(position, column, title)
+        self.assertEqual(len(self.table.columns), 4)
+        self.compare_iterable(column, self.table.columns[position])
 
     def test_pop_column_by_position(self):
         position = 2
-        header = self.table.get_column_header(position)
-        self.table.pop_column(position)
-        self.assertEqual(self.table.column_count, 2)
+        header = self.table.columns.header[position]
+        self.table.columns.pop(position)
+        self.assertEqual(len(self.table.columns), 2)
         with self.assertRaises(KeyError):
-            self.table.get_column(header)
+            self.table.columns[header]
 
     def test_pop_column_by_header(self):
         header = "gender"
-        self.table.pop_column(header)
-        self.assertEqual(self.table.column_count, 2)
+        self.table.columns.pop(header)
+        self.assertEqual(len(self.table.columns), 2)
         with self.assertRaises(KeyError):
-            self.table.get_column(header)
+            self.table.columns[header]
 
-    def test_update_column(self):
+    def test_update_column_by_index(self):
+        index = 1
+        column = [3, 2, 1, 2, 4]
+        self.table.columns.update(index, column)
+        self.assertEqual(len(self.table.columns), 3)
+        self.compare_iterable(column, self.table.columns[index])
+
+    def test_update_column_by_header(self):
         header = "rank"
         column = [3, 2, 1, 2, 4]
-        self.table.update_column(header, column)
-        self.assertEqual(self.table.column_count, 3)
-        self.compare_iterable(column, self.table.get_column(header))
+        self.table.columns.update(header, column)
+        self.assertEqual(len(self.table.columns), 3)
+        self.compare_iterable(column, self.table.columns[header])
+
+    def test_update_column_slice(self):
+        columns = [
+            [5, "girl"],
+            [4, "boy"],
+            [4, "boy"],
+            [2, "girl"],
+            [1, "boy"],
+        ]
+        self.table.columns.update(slice(1, 3, 1), columns)
+        self.assertEqual(len(self.table.columns), 3)
+        self.compare_iterable(self.table.columns[1], [c[0] for c in columns])
+        self.compare_iterable(self.table.columns[2], [c[1] for c in columns])
 
     # Tests for row operations
 
     def test_row_count(self):
-        self.assertEqual(len(self.table), 5)
+        self.assertEqual(len(self.table.rows), 5)
 
     def test_access_row_by_index(self):
         row = ["Sophia", 2, "girl"]
-        self.compare_iterable(row, self.table[3])
+        self.compare_iterable(row, self.table.rows[3])
+        with self.assertRaises(IndexError):
+            self.table.rows[len(self.table.rows)]
+
+    def test_access_row_by_header(self):
+        row = ["Sophia", 2, "girl"]
+        self.compare_iterable(row, self.table.rows["S4"])
         with self.assertRaises(IndexError):
-            self.table[len(self.table)]
+            self.table.rows[len(self.table.rows)]
 
     def test_access_row_element_by_index(self):
-        self.assertEqual(self.table[2][0], "Ethan")
+        self.assertEqual(self.table.rows[2][0], "Ethan")
 
     def test_access_row_element_by_header(self):
-        self.assertEqual(self.table[2]["name"], "Ethan")
+        self.assertEqual(self.table.rows[2]["name"], "Ethan")
 
     def test_append_row(self):
         row = ["Gary", 2, "boy"]
-        self.table.append_row(row)
-        self.assertEqual(len(self.table), 6)
-        self.compare_iterable(self.table[5], row)
+        self.table.rows.append(row, header="S6")
+        self.assertEqual(len(self.table.rows), 6)
+        self.compare_iterable(self.table.rows[5], row)
 
     def test_insert_row(self):
         row = ["Gary", 2, "boy"]
         position = 2
-        self.table.insert_row(position, row)
-        self.assertEqual(len(self.table), 6)
-        self.compare_iterable(self.table[position], row)
+        self.table.rows.insert(position, row, header="S6")
+        self.assertEqual(len(self.table.rows), 6)
+        self.compare_iterable(self.table.rows[position], row)
 
-    def test_pop_row(self):
+    def test_pop_row_by_position(self):
         position = 2
-        self.table.pop_row(position)
-        self.assertEqual(len(self.table), 4)
+        self.table.rows.pop(position)
+        self.assertEqual(len(self.table.rows), 4)
+
+    def test_pop_row_by_header(self):
+        header = "S3"
+        self.table.rows.pop(header)
+        self.assertEqual(len(self.table.rows), 4)
 
-    def test_update_row(self):
+    def test_update_row_by_index(self):
         row = ["Sophie", 5, "girl"]
         position = 3
-        self.table.update_row(position, row)
-        self.compare_iterable(self.table[position], row)
+        self.table.rows.update(position, row)
+        self.compare_iterable(self.table.rows[position], row)
+
+    def test_update_row_by_header(self):
+        row = ["Sophie", 5, "girl"]
+        header = "S4"
+        self.table.rows.update(header, row)
+        self.compare_iterable(self.table.rows[header], row)
 
     def test_update_row_slice(self):
         rows = [["Sophie", 5, "girl"], ["Mike", 4, "boy"]]
-        self.table.update_row(slice(3, 5, 1), rows)
-        self.assertEqual(len(self.table), 5)
-        self.compare_iterable(self.table[3], rows[0])
-        self.compare_iterable(self.table[4], rows[1])
+        self.table.rows.update(slice(3, 5, 1), rows)
+        self.assertEqual(len(self.table.rows), 5)
+        self.compare_iterable(self.table.rows[3], rows[0])
+        self.compare_iterable(self.table.rows[4], rows[1])
 
-    # Tests for special methods
+    # Tests for special row methods
 
-    def test_getitem_slice(self):
-        new_table = self.table[:3]
-        self.assertEqual(len(new_table), 3)
-        self.assertEqual(self.table.column_count, 3)
+    def test_row_getitem_slice(self):
+        new_table = self.table.rows[:3]
+        self.assertEqual(len(new_table.rows), 3)
+        self.assertEqual(len(self.table.rows), 5)
 
-    def test_delitem_int(self):
-        del self.table[1]
-        self.assertEqual(len(self.table), 4)
+    def test_row_delitem_int(self):
+        del self.table.rows[1]
+        self.assertEqual(len(self.table.rows), 4)
 
-    def test_delitem_slice(self):
-        del self.table[2:]
-        self.assertEqual(len(self.table), 2)
+    def test_row_delitem_slice(self):
+        del self.table.rows[2:]
+        self.assertEqual(len(self.table.rows), 2)
 
-    def test_delitem_str(self):
-        del self.table["rank"]
-        self.assertEqual(self.table.column_count, 2)
+    def test_row_delitem_str(self):
+        del self.table.rows["S2"]
+        self.assertEqual(len(self.table.rows), 4)
         with self.assertRaises(KeyError):
-            self.table.get_column("rank")
+            self.table.rows["S2"]
 
-    def test_setitem_int(self):
+    def test_row_setitem_int(self):
         position = 3
         row = ["Sophie", 5, "girl"]
-        self.table[position] = row
-        self.compare_iterable(self.table[position], row)
+        self.table.rows[position] = row
+        self.compare_iterable(self.table.rows[position], row)
 
-    def test_setitem_slice(self):
+    def test_row_setitem_slice(self):
         rows = [["Sophie", 5, "girl"], ["Mike", 4, "boy"]]
-        self.table[3:] = rows
-        self.assertEqual(len(self.table), 5)
-        self.compare_iterable(self.table[3], rows[0])
-        self.compare_iterable(self.table[4], rows[1])
+        self.table.rows[3:] = rows
+        self.assertEqual(len(self.table.rows), 5)
+        self.compare_iterable(self.table.rows[3], rows[0])
+        self.compare_iterable(self.table.rows[4], rows[1])
+
+    def test_row_setitem_str(self):
+        header = "S2"
+        row = ["Mike", 4, "Boy"]
+        self.table.rows[header] = row
+        self.assertEqual(len(self.table.rows), 5)
+        self.compare_iterable(row, self.table.rows[header])
+
+    def test_row_contains(self):
+        self.assertTrue(["Isabella", 1, "girl"] in self.table.rows)
+        self.assertFalse(["Ethan", 3, "boy"] in self.table.rows)
+
+    def test_row_header_contains(self):
+        self.assertTrue("S3" in self.table.rows.header)
+        self.assertFalse("S6" in self.table.rows.header)
+
+    # Test for special column methods
+
+    def test_column_getitem_slice(self):
+        new_table = self.table.columns[:2]
+        self.assertEqual(len(self.table.columns), 3)
+        self.assertEqual(len(new_table.columns), 2)
+
+    def test_column_delitem_int(self):
+        del self.table.columns[1]
+        self.assertEqual(len(self.table.columns), 2)
+
+    def test_column_delitem_slice(self):
+        del self.table.columns[2:]
+        self.assertEqual(len(self.table.columns), 2)
+
+    def test_column_delitem_str(self):
+        del self.table.columns["rank"]
+        self.assertEqual(len(self.table.columns), 2)
+        with self.assertRaises(KeyError):
+            self.table.columns["rank"]
+
+    def test_column_setitem_int(self):
+        position = 2
+        row = [3, 4, 5, 6, 7]
+        self.table.columns[position] = row
+        self.compare_iterable(self.table.columns[position], row)
+
+    def test_column_setitem_slice(self):
+        columns = [
+            [5, "girl"],
+            [4, "boy"],
+            [4, "boy"],
+            [2, "girl"],
+            [1, "boy"],
+        ]
+        self.table.columns[1:] = columns
+        self.assertEqual(len(self.table.columns), 3)
+        self.compare_iterable(self.table.columns[1], [c[0] for c in columns])
+        self.compare_iterable(self.table.columns[2], [c[1] for c in columns])
 
-    def test_setitem_str(self):
+    def test_column_setitem_str(self):
         header = "rank"
         column = [3, 2, 1, 2, 4]
-        self.table[header] = column
-        self.assertEqual(self.table.column_count, 3)
-        self.compare_iterable(column, self.table.get_column(header))
-
-    def test_contains(self):
-        self.assertTrue("rank" in self.table)
-        self.assertFalse("rankk" in self.table)
-        self.assertTrue(["Isabella", 1, "girl"] in self.table)
-        self.assertFalse(["Ethan", 3, "boy"] in self.table)
+        self.table.columns[header] = column
+        self.assertEqual(len(self.table.columns), 3)
+        self.compare_iterable(column, self.table.columns[header])
+
+    def test_column_header_contains(self):
+        self.assertTrue("rank" in self.table.columns.header)
+        self.assertFalse("score" in self.table.columns.header)
+
+    def test_column_contains(self):
+        self.assertTrue(
+            ["boy", "girl", "boy", "girl", "boy"] in self.table.columns
+        )
+        self.assertFalse(
+            ["boy", "girl", "girl", "girl", "boy"] in self.table.columns
+        )
 
     # Test for printing operations
 
     def test_get_string(self):
-        string = """+----------+------+--------+
-|   name   | rank | gender |
-+----------+------+--------+
-|  Jacob   |  1   |  boy   |
-+----------+------+--------+
-| Isabella |  1   |  girl  |
-+----------+------+--------+
-|  Ethan   |  2   |  boy   |
-+----------+------+--------+
-|  Sophia  |  2   |  girl  |
-+----------+------+--------+
-| Michael  |  3   |  boy   |
-+----------+------+--------+"""
+        string = """+----+----------+------+--------+
+|    |   name   | rank | gender |
++----+----------+------+--------+
+| S1 |  Jacob   |  1   |  boy   |
++----+----------+------+--------+
+| S2 | Isabella |  1   |  girl  |
++----+----------+------+--------+
+| S3 |  Ethan   |  2   |  boy   |
++----+----------+------+--------+
+| S4 |  Sophia  |  2   |  girl  |
++----+----------+------+--------+
+| S5 | Michael  |  3   |  boy   |
++----+----------+------+--------+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_left_align(self):
-        self.table.column_alignments[0] = self.table.ALIGN_LEFT
-        string = """+----------+------+--------+
-| name     | rank | gender |
-+----------+------+--------+
-| Jacob    |  1   |  boy   |
-+----------+------+--------+
-| Isabella |  1   |  girl  |
-+----------+------+--------+
-| Ethan    |  2   |  boy   |
-+----------+------+--------+
-| Sophia   |  2   |  girl  |
-+----------+------+--------+
-| Michael  |  3   |  boy   |
-+----------+------+--------+"""
+        self.table.columns.alignment[0] = self.table.ALIGN_LEFT
+        string = """+----+----------+------+--------+
+|    | name     | rank | gender |
++----+----------+------+--------+
+| S1 | Jacob    |  1   |  boy   |
++----+----------+------+--------+
+| S2 | Isabella |  1   |  girl  |
++----+----------+------+--------+
+| S3 | Ethan    |  2   |  boy   |
++----+----------+------+--------+
+| S4 | Sophia   |  2   |  girl  |
++----+----------+------+--------+
+| S5 | Michael  |  3   |  boy   |
++----+----------+------+--------+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_right_align(self):
-        self.table.column_alignments[0] = self.table.ALIGN_RIGHT
-        string = """+----------+------+--------+
-|     name | rank | gender |
-+----------+------+--------+
-|    Jacob |  1   |  boy   |
-+----------+------+--------+
-| Isabella |  1   |  girl  |
-+----------+------+--------+
-|    Ethan |  2   |  boy   |
-+----------+------+--------+
-|   Sophia |  2   |  girl  |
-+----------+------+--------+
-|  Michael |  3   |  boy   |
-+----------+------+--------+"""
+        self.table.columns.alignment[0] = self.table.ALIGN_RIGHT
+        string = """+----+----------+------+--------+
+|    |     name | rank | gender |
++----+----------+------+--------+
+| S1 |    Jacob |  1   |  boy   |
++----+----------+------+--------+
+| S2 | Isabella |  1   |  girl  |
++----+----------+------+--------+
+| S3 |    Ethan |  2   |  boy   |
++----+----------+------+--------+
+| S4 |   Sophia |  2   |  girl  |
++----+----------+------+--------+
+| S5 |  Michael |  3   |  boy   |
++----+----------+------+--------+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_mixed_align(self):
-        self.table.column_alignments = [
+        self.table.columns.alignment = [
             self.table.ALIGN_LEFT,
             self.table.ALIGN_CENTER,
             self.table.ALIGN_RIGHT,
         ]
-        string = """+----------+------+--------+
-| name     | rank | gender |
-+----------+------+--------+
-| Jacob    |  1   |    boy |
-+----------+------+--------+
-| Isabella |  1   |   girl |
-+----------+------+--------+
-| Ethan    |  2   |    boy |
-+----------+------+--------+
-| Sophia   |  2   |   girl |
-+----------+------+--------+
-| Michael  |  3   |    boy |
-+----------+------+--------+"""
+        string = """+----+----------+------+--------+
+|    | name     | rank | gender |
++----+----------+------+--------+
+| S1 | Jacob    |  1   |    boy |
++----+----------+------+--------+
+| S2 | Isabella |  1   |   girl |
++----+----------+------+--------+
+| S3 | Ethan    |  2   |    boy |
++----+----------+------+--------+
+| S4 | Sophia   |  2   |   girl |
++----+----------+------+--------+
+| S5 | Michael  |  3   |    boy |
++----+----------+------+--------+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_align_all(self):
-        self.table.column_alignments = self.table.ALIGN_LEFT
-        string = """+----------+------+--------+
-| name     | rank | gender |
-+----------+------+--------+
-| Jacob    | 1    | boy    |
-+----------+------+--------+
-| Isabella | 1    | girl   |
-+----------+------+--------+
-| Ethan    | 2    | boy    |
-+----------+------+--------+
-| Sophia   | 2    | girl   |
-+----------+------+--------+
-| Michael  | 3    | boy    |
-+----------+------+--------+"""
+        self.table.columns.alignment = self.table.ALIGN_LEFT
+        string = """+----+----------+------+--------+
+|    | name     | rank | gender |
++----+----------+------+--------+
+| S1 | Jacob    | 1    | boy    |
++----+----------+------+--------+
+| S2 | Isabella | 1    | girl   |
++----+----------+------+--------+
+| S3 | Ethan    | 2    | boy    |
++----+----------+------+--------+
+| S4 | Sophia   | 2    | girl   |
++----+----------+------+--------+
+| S5 | Michael  | 3    | boy    |
++----+----------+------+--------+"""
         self.assertEqual(string, self.table.get_string())
 
-    def test_signmode_plus(self):
-        self.table.sign_mode = self.table.SM_PLUS
-        string = """+----------+------+--------+
-|   name   | rank | gender |
-+----------+------+--------+
-|  Jacob   |  +1  |  boy   |
-+----------+------+--------+
-| Isabella |  +1  |  girl  |
-+----------+------+--------+
-|  Ethan   |  +2  |  boy   |
-+----------+------+--------+
-|  Sophia  |  +2  |  girl  |
-+----------+------+--------+
-| Michael  |  +3  |  boy   |
-+----------+------+--------+"""
+    def test_sign_plus(self):
+        self.table.sign = self.table.SM_PLUS
+        string = """+----+----------+------+--------+
+|    |   name   | rank | gender |
++----+----------+------+--------+
+| S1 |  Jacob   |  +1  |  boy   |
++----+----------+------+--------+
+| S2 | Isabella |  +1  |  girl  |
++----+----------+------+--------+
+| S3 |  Ethan   |  +2  |  boy   |
++----+----------+------+--------+
+| S4 |  Sophia  |  +2  |  girl  |
++----+----------+------+--------+
+| S5 | Michael  |  +3  |  boy   |
++----+----------+------+--------+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_wep_wrap(self):
         self.create_table(20)
-        self.table.width_exceed_policy = self.table.WEP_WRAP
-        string = """+-------+----+-----+
-| name  | ra | gen |
-|       | nk | der |
-+-------+----+-----+
-| Jacob | 1  | boy |
-+-------+----+-----+
-| Isabe | 1  | gir |
-|  lla  |    |  l  |
-+-------+----+-----+
-| Ethan | 2  | boy |
-+-------+----+-----+
-| Sophi | 2  | gir |
-|   a   |    |  l  |
-+-------+----+-----+
-| Micha | 3  | boy |
-|  el   |    |     |
-+-------+----+-----+"""
+        self.table.columns.width_exceed_policy = self.table.WEP_WRAP
+        string = """+---+------+---+---+
+|   | name | r | g |
+|   |      | a | e |
+|   |      | n | n |
+|   |      | k | d |
+|   |      |   | e |
+|   |      |   | r |
++---+------+---+---+
+| S | Jaco | 1 | b |
+| 1 |  b   |   | o |
+|   |      |   | y |
++---+------+---+---+
+| S | Isab | 1 | g |
+| 2 | ella |   | i |
+|   |      |   | r |
+|   |      |   | l |
++---+------+---+---+
+| S | Etha | 2 | b |
+| 3 |  n   |   | o |
+|   |      |   | y |
++---+------+---+---+
+| S | Soph | 2 | g |
+| 4 |  ia  |   | i |
+|   |      |   | r |
+|   |      |   | l |
++---+------+---+---+
+| S | Mich | 3 | b |
+| 5 | ael  |   | o |
+|   |      |   | y |
++---+------+---+---+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_wep_strip(self):
         self.create_table(20)
-        self.table.width_exceed_policy = self.table.WEP_STRIP
-        string = """+-------+----+-----+
-| name  | ra | gen |
-+-------+----+-----+
-| Jacob | 1  | boy |
-+-------+----+-----+
-| Isabe | 1  | gir |
-+-------+----+-----+
-| Ethan | 2  | boy |
-+-------+----+-----+
-| Sophi | 2  | gir |
-+-------+----+-----+
-| Micha | 3  | boy |
-+-------+----+-----+"""
+        self.table.columns.width_exceed_policy = self.table.WEP_STRIP
+        string = """+---+------+---+---+
+|   | name | r | g |
++---+------+---+---+
+| S | Jaco | 1 | b |
++---+------+---+---+
+| S | Isab | 1 | g |
++---+------+---+---+
+| S | Etha | 2 | b |
++---+------+---+---+
+| S | Soph | 2 | g |
++---+------+---+---+
+| S | Mich | 3 | b |
++---+------+---+---+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_wep_ellipsis(self):
         self.create_table(20)
-        self.table.width_exceed_policy = self.table.WEP_ELLIPSIS
-        string = """+-------+----+-----+
-| name  | .. | ... |
-+-------+----+-----+
-| Jacob | 1  | boy |
-+-------+----+-----+
-| Is... | 1  | ... |
-+-------+----+-----+
-| Ethan | 2  | boy |
-+-------+----+-----+
-| So... | 2  | ... |
-+-------+----+-----+
-| Mi... | 3  | boy |
-+-------+----+-----+"""
+        self.table.columns.width_exceed_policy = self.table.WEP_ELLIPSIS
+        string = """+---+------+---+---+
+|   | name | . | . |
++---+------+---+---+
+| . | J... | 1 | . |
++---+------+---+---+
+| . | I... | 1 | . |
++---+------+---+---+
+| . | E... | 2 | . |
++---+------+---+---+
+| . | S... | 2 | . |
++---+------+---+---+
+| . | M... | 3 | . |
++---+------+---+---+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_empty_header(self):
-        self.table.column_headers = ["", " ", "  "]
-        string = """+----------+---+------+
-|  Jacob   | 1 | boy  |
-+----------+---+------+
-| Isabella | 1 | girl |
-+----------+---+------+
-|  Ethan   | 2 | boy  |
-+----------+---+------+
-|  Sophia  | 2 | girl |
-+----------+---+------+
-| Michael  | 3 | boy  |
-+----------+---+------+"""
-        self.assertEqual(string, self.table.get_string())
-
-    def test_serialno(self):
-        self.table.serialno = True
-        string = """+----+----------+------+--------+
-| SN |   name   | rank | gender |
-+----+----------+------+--------+
-| 1  |  Jacob   |  1   |  boy   |
-+----+----------+------+--------+
-| 2  | Isabella |  1   |  girl  |
-+----+----------+------+--------+
-| 3  |  Ethan   |  2   |  boy   |
-+----+----------+------+--------+
-| 4  |  Sophia  |  2   |  girl  |
-+----+----------+------+--------+
-| 5  | Michael  |  3   |  boy   |
-+----+----------+------+--------+"""
+        self.table.columns.header = ["", " ", "  "]
+        string = """+----+----------+---+------+
+| S1 |  Jacob   | 1 | boy  |
++----+----------+---+------+
+| S2 | Isabella | 1 | girl |
++----+----------+---+------+
+| S3 |  Ethan   | 2 | boy  |
++----+----------+---+------+
+| S4 |  Sophia  | 2 | girl |
++----+----------+---+------+
+| S5 | Michael  | 3 | boy  |
++----+----------+---+------+"""
         self.assertEqual(string, self.table.get_string())
 
     def test_eastasian_characters(self):
-        string = u"""+------------+------+--------+
-|    name    | rank | gender |
-+------------+------+--------+
-|   Jacob    |  1   |  boy   |
-+------------+------+--------+
-|  Isabella  |  1   |  girl  |
-+------------+------+--------+
-|   Ethan    |  2   |  boy   |
-+------------+------+--------+
-|   Sophia   |  2   |  girl  |
-+------------+------+--------+
-|  Michael   |  3   |  boy   |
-+------------+------+--------+
-| こんにちは |  2   |  boy   |
-+------------+------+--------+"""
-        self.table.append_row([u"こんにちは", 2, "boy"])
+        string = u"""+----+------------+------+--------+
+|    |    name    | rank | gender |
++----+------------+------+--------+
+| S1 |   Jacob    |  1   |  boy   |
++----+------------+------+--------+
+| S2 |  Isabella  |  1   |  girl  |
++----+------------+------+--------+
+| S3 |   Ethan    |  2   |  boy   |
++----+------------+------+--------+
+| S4 |   Sophia   |  2   |  girl  |
++----+------------+------+--------+
+| S5 |  Michael   |  3   |  boy   |
++----+------------+------+--------+
+| S6 | こんにちは |  2   |  boy   |
++----+------------+------+--------+"""
+        self.table.rows.append([u"こんにちは", 2, "boy"], header="S6")
         self.assertEqual(string, self.table.get_string())
 
     def test_newline(self):
@@ -466,7 +570,7 @@ class TableOperationsTestCase(unittest.TestCase):
 |   | b |
 +---+---+"""
         table = BeautifulTable()
-        table.append_row(["0", "a\nb"])
+        table.rows.append(["0", "a\nb"])
         self.assertEqual(string, table.get_string())
 
     def test_newline_multiple_columns(self):
@@ -476,7 +580,7 @@ class TableOperationsTestCase(unittest.TestCase):
 | c |   |
 +---+---+"""
         table = BeautifulTable()
-        table.append_row(["a\nb\nc", "p\nq"])
+        table.rows.append(["a\nb\nc", "p\nq"])
         self.assertEqual(string, table.get_string())
 
     # Test for ANSI sequences
@@ -486,67 +590,67 @@ class TableOperationsTestCase(unittest.TestCase):
         string = """+------+---+-----+
 | \x1b[31mAdam\x1b[0m | 2 | boy |
 +------+---+-----+"""
-        table.append_row(["\x1b[31mAdam\x1b[0m", 2, "boy"])
+        table.rows.append(["\x1b[31mAdam\x1b[0m", 2, "boy"])
         self.assertEqual(string, table.get_string())
 
     def test_ansi_wrap(self):
-        table = BeautifulTable(max_width=30)
+        table = BeautifulTable(maxwidth=30)
         string = """+-----------------+---+------+
 | \x1b[31mThis is a very \x1b[0m | 2 | girl |
 |    \x1b[32mlong name\x1b[0m    |   |      |
 +-----------------+---+------+"""
         long_string = "\x1b[31mThis is a very \x1b[0m\x1b[32mlong name\x1b[0m"
-        table.append_row([long_string, 2, "girl"])
+        table.rows.append([long_string, 2, "girl"])
         self.assertEqual(string, table.get_string())
 
     def test_ansi_wrap_mb(self):
-        table = BeautifulTable(max_width=30)
+        table = BeautifulTable(maxwidth=30)
         string = u"""+-----------------+---+------+
 | \x1b[31mこれは非常に長\x1b[0m  | 2 | girl |
 |   \x1b[31mい\x1b[0m\x1b[32m名前です\x1b[0m    |   |      |
 +-----------------+---+------+"""
         long_string = u"\x1b[31mこれは非常に長い\x1b[0m\x1b[32m名前です\x1b[0m"
-        table.append_row([long_string, 2, "girl"])
+        table.rows.append([long_string, 2, "girl"])
         self.assertEqual(string, table.get_string())
 
     def test_ansi_ellipsis(self):
-        table = BeautifulTable(max_width=30)
-        table.width_exceed_policy = table.WEP_ELLIPSIS
+        table = BeautifulTable(maxwidth=30)
+        table.columns.width_exceed_policy = table.WEP_ELLIPSIS
         string = """+-----------------+---+------+
 | \x1b[31mThis is a ve\x1b[0m... | 2 | girl |
 +-----------------+---+------+"""
         long_string = "\x1b[31mThis is a very \x1b[0m\x1b[32mlong name\x1b[0m"
-        table.append_row([long_string, 2, "girl"])
+        table.rows.append([long_string, 2, "girl"])
         self.assertEqual(string, table.get_string())
 
     def test_ansi_ellipsis_mb(self):
-        table = BeautifulTable(max_width=30)
-        table.width_exceed_policy = table.WEP_ELLIPSIS
+        table = BeautifulTable(maxwidth=30)
+        table.columns.width_exceed_policy = table.WEP_ELLIPSIS
         string = u"""+-----------------+---+------+
 | \x1b[31mこれは非常に\x1b[0m... | 2 | girl |
 +-----------------+---+------+"""
         long_string = u"\x1b[31mこれは非常に長い\x1b[0m\x1b[32m名前です\x1b[0m"
-        table.append_row([long_string, 2, "girl"])
+        table.rows.append([long_string, 2, "girl"])
         self.assertEqual(string, table.get_string())
 
     def test_ansi_strip(self):
-        table = BeautifulTable(max_width=30)
-        table.width_exceed_policy = table.WEP_STRIP
+        table = BeautifulTable(maxwidth=30)
+        table.columns.width_exceed_policy = table.WEP_STRIP
         string = """+-----------------+---+------+
 | \x1b[31mThis is a very \x1b[0m | 2 | girl |
 +-----------------+---+------+"""
         long_string = "\x1b[31mThis is a very \x1b[0m\x1b[32mlong name\x1b[0m"
-        table.append_row([long_string, 2, "girl"])
+        table.rows.append([long_string, 2, "girl"])
         self.assertEqual(string, table.get_string())
 
     def test_ansi_strip_mb(self):
-        table = BeautifulTable(max_width=30)
-        table.width_exceed_policy = table.WEP_STRIP
+        table = BeautifulTable(maxwidth=30)
+        table.columns.width_exceed_policy = table.WEP_STRIP
         string = u"""+-----------------+---+------+
 | \x1b[31mこれは非常に長\x1b[0m  | 2 | girl |
 +-----------------+---+------+"""
         long_string = u"\x1b[31mこれは非常に長い\x1b[0m\x1b[32m名前です\x1b[0m"
-        table.append_row([long_string, 2, "girl"])
+        table.rows.append([long_string, 2, "girl"])
         self.assertEqual(string, table.get_string())
 
     # Test on empty table
@@ -554,30 +658,29 @@ class TableOperationsTestCase(unittest.TestCase):
     def test_empty_table_by_column(self):
         self.create_table(20)
         for i in range(3):
-            self.table.pop_column()
+            self.table.columns.pop()
         self.assertEqual(self.table.get_string(), "")
 
     def test_empty_table_by_row(self):
         self.create_table(20)
         for i in range(5):
-            self.table.pop_row()
+            self.table.rows.pop()
         self.assertEqual(self.table.get_string(), "")
 
     def test_table_width_zero(self):
         self.create_table(20)
         self.table.clear(True)
-        width = self.table.get_table_width()
-        self.assertEqual(width, 0)
+        self.assertEqual(self.table.width, 0)
 
     def test_table_auto_width(self):
         row_list = ["abcdefghijklmopqrstuvwxyz", 1234, "none"]
 
         self.create_table(200)
-        self.table.append_row(row_list)
+        self.table.rows.append(row_list)
         len_for_max_width_200 = len(str(self.table))
 
         self.create_table(80)
-        self.table.append_row(row_list)
+        self.table.rows.append(row_list)
         len_for_max_width_80 = len(str(self.table))
 
         self.assertEqual(len_for_max_width_80, len_for_max_width_200)
@@ -608,15 +711,12 @@ class TableOperationsTestCase(unittest.TestCase):
         with self.assertRaises(ValueError):
             self.table.from_csv(1)
 
-        self.assertEqual(len(self.table), len(test_table))
-        self.assertEqual(self.table.column_headers, test_table.column_headers)
-
-        # for index in range(len(self.table)):
-        #     self.assertEqual(self.table[index], test_table[index])
+        self.assertEqual(len(self.table.rows), len(test_table.rows))
+        self.assertEqual(self.table.columns.header, test_table.columns.header)
 
         test_table = BeautifulTable()
-        test_table.from_csv("beautiful_table.csv", header_exists=False)
-        self.assertEqual(len(self.table), len(test_table) - 1)
+        test_table.from_csv("beautiful_table.csv", header=False)
+        self.assertEqual(len(self.table.rows), len(test_table.rows) - 1)
 
         # Teardown step.
         os.remove("beautiful_table.csv")