Misc tips

Misc tips
Author

Nipun Batra

Published

January 10, 2023

Miscelleneous tips

  • Tab Complete
  • a.b?
  • Black format
  • rich
    • table
    • inspect
  • dir()
  • timing
  • timing with sort?!
  • timeit object
  • plotting timing
  • array .tolist()
  • Dataclasses
  • Annotation
  • Plotting with pandas (sin, log, question …)
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd

%matplotlib inline
%config InlineBackend.figure_format='retina'
n = 100
k = 20
m = 50

np.random.seed(0)
a = np.random.randn(n, k)
b = np.random.randn(k, m)
a.shape, b.shape
((100, 20), (20, 50))
a_list_of_list = a.tolist()
a_list_of_list[0]
list
"list" in dir(a)[:]
False
import rich
rich.inspect(a.tolist, methods=True, all=True)
╭──────────────────────── <built-in method tolist of numpy.ndarray object at 0x164cbb8d0> ────────────────────────╮
 def ndarray.tolist(...)                                                                                         
                                                                                                                 
 a.tolist()                                                                                                      
                                                                                                                 
            __doc__ = "a.tolist()\n\n    Return the array as an ``a.ndim``-levels deep nested list of Python     
                      scalars.\n\n    Return a copy of the array data as a (nested) Python list.\n    Data items 
                      are converted to the nearest compatible builtin Python type, via\n    the                  
                      `~numpy.ndarray.item` function.\n\n    If ``a.ndim`` is 0, then since the depth of the     
                      nested list is 0, it will\n    not be a list at all, but a simple Python scalar.\n\n       
                      Parameters\n    ----------\n    none\n\n    Returns\n    -------\n    y : object, or list  
                      of object, or list of list of object, or ...\n        The possibly nested list of array    
                      elements.\n\n    Notes\n    -----\n    The array may be recreated via ``a =                
                      np.array(a.tolist())``, although this\n    may sometimes lose precision.\n\n    Examples\n 
                      --------\n    For a 1D array, ``a.tolist()`` is almost the same as ``list(a)``,\n          
                      except that ``tolist`` changes numpy scalars to Python scalars:\n\n    >>> a =             
                      np.uint32([1, 2])\n    >>> a_list = list(a)\n    >>> a_list\n    [1, 2]\n    >>>           
                      type(a_list[0])\n    <class 'numpy.uint32'>\n    >>> a_tolist = a.tolist()\n    >>>        
                      a_tolist\n    [1, 2]\n    >>> type(a_tolist[0])\n    <class 'int'>\n\n    Additionally,    
                      for a 2D array, ``tolist`` applies recursively:\n\n    >>> a = np.array([[1, 2], [3,       
                      4]])\n    >>> list(a)\n    [array([1, 2]), array([3, 4])]\n    >>> a.tolist()\n    [[1,    
                      2], [3, 4]]\n\n    The base case for this recursion is a 0D array:\n\n    >>> a =          
                      np.array(1)\n    >>> list(a)\n    Traceback (most recent call last):\n      ...\n          
                      TypeError: iteration over a 0-d array\n    >>> a.tolist()\n    1"                          
         __module__ = None                                                                                       
           __name__ = 'tolist'                                                                                   
       __qualname__ = 'ndarray.tolist'                                                                           
           __self__ = array([[ 1.76405235e+00,  4.00157208e-01,  9.78737984e-01, ...,                            
                              -2.05158264e-01,  3.13067702e-01, -8.54095739e-01],                                
                             [-2.55298982e+00,  6.53618595e-01,  8.64436199e-01, ...,                            
                               1.20237985e+00, -3.87326817e-01, -3.02302751e-01],                                
                             [-1.04855297e+00, -1.42001794e+00, -1.70627019e+00, ...,                            
                               3.02471898e-01, -6.34322094e-01, -3.62741166e-01],                                
                             ...,                                                                                
                             [ 8.73311836e-01,  1.19973618e+00,  4.56153036e-01, ...,                            
                              -1.24021634e+00,  9.00054243e-01,  1.80224223e+00],                                
                             [-2.08285103e-01,  1.57437124e+00,  1.98989494e-01, ...,                            
                               4.32837621e-01, -8.08717532e-01, -1.10412399e+00],                                
                             [-7.89102180e-01,  1.24845579e-03, -1.59939788e-01, ...,                            
                               1.58433847e-01, -1.14190142e+00, -1.31097037e+00]])                               
 __text_signature__ = None                                                                                       
           __call__ = def __call__(*args, **kwargs): Call self as a function.                                    
          __class__ = class __class__():                                                                         
        __delattr__ = def __delattr__(name, /): Implement delattr(self, name).                                   
            __dir__ = def __dir__(): Default dir() implementation.                                               
             __eq__ = def __eq__(value, /): Return self==value.                                                  
         __format__ = def __format__(format_spec, /): Default object formatter.                                  
             __ge__ = def __ge__(value, /): Return self>=value.                                                  
   __getattribute__ = def __getattribute__(name, /): Return getattr(self, name).                                 
             __gt__ = def __gt__(value, /): Return self>value.                                                   
           __hash__ = def __hash__(): Return hash(self).                                                         
           __init__ = def __init__(*args, **kwargs): Initialize self.  See help(type(self)) for accurate         
                      signature.                                                                                 
  __init_subclass__ = def __init_subclass__(...) This method is called when a class is subclassed.               
             __le__ = def __le__(value, /): Return self<=value.                                                  
             __lt__ = def __lt__(value, /): Return self<value.                                                   
             __ne__ = def __ne__(value, /): Return self!=value.                                                  
            __new__ = def __new__(*args, **kwargs): Create and return a new object.  See help(type) for accurate 
                      signature.                                                                                 
         __reduce__ = def __reduce__(...) Helper for pickle.                                                     
      __reduce_ex__ = def __reduce_ex__(protocol, /): Helper for pickle.                                         
           __repr__ = def __repr__(): Return repr(self).                                                         
        __setattr__ = def __setattr__(name, value, /): Implement setattr(self, name, value).                     
         __sizeof__ = def __sizeof__(): Size of object in memory, in bytes.                                      
            __str__ = def __str__(): Return str(self).                                                           
   __subclasshook__ = def __subclasshook__(...) Abstract classes can override this to customize issubclass().    
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
lambda x: np.power(x, 3)


def f(x):
    return np.power(x, 3)
d = {
    r"$\sin(x)$": np.sin,
    r"$\log(x)$": np.log,
    r"$\frac{sin(x)}{x}$": lambda x: np.sin(x) / x,
    r"$x^3$": lambda x: np.power(x, 3),
}
d
{'$\\sin(x)$': <ufunc 'sin'>,
 '$\\log(x)$': <ufunc 'log'>,
 '$\\frac{sin(x)}{x}$': <function __main__.<lambda>(x)>,
 '$x^3$': <function __main__.<lambda>(x)>}
x = np.arange(0.005, 10.0, 0.005)
e = {k: v(x) for k, v in d.items()}
e
{'$\\sin(x)$': array([ 0.00499998,  0.00999983,  0.01499944, ..., -0.53137431,
        -0.53560333, -0.53981897]),
 '$\\log(x)$': array([-5.29831737, -4.60517019, -4.19970508, ...,  2.30108397,
         2.30158459,  2.30208497]),
 '$\\frac{sin(x)}{x}$': array([ 0.99999583,  0.99998333,  0.9999625 , ..., -0.05321726,
        -0.05361395, -0.0540089 ]),
 '$x^3$': array([1.25000000e-07, 1.00000000e-06, 3.37500000e-06, ...,
        9.95506747e+02, 9.97002999e+02, 9.98500750e+02])}
df = pd.DataFrame(e, index=x)
df
$\sin(x)$ $\log(x)$ $\frac{sin(x)}{x}$ $x^3$
0.005 0.005000 -5.298317 0.999996 1.250000e-07
0.010 0.010000 -4.605170 0.999983 1.000000e-06
0.015 0.014999 -4.199705 0.999963 3.375000e-06
0.020 0.019999 -3.912023 0.999933 8.000000e-06
0.025 0.024997 -3.688879 0.999896 1.562500e-05
... ... ... ... ...
9.975 -0.522877 2.300082 -0.052419 9.925187e+02
9.980 -0.527132 2.300583 -0.052819 9.940120e+02
9.985 -0.531374 2.301084 -0.053217 9.955067e+02
9.990 -0.535603 2.301585 -0.053614 9.970030e+02
9.995 -0.539819 2.302085 -0.054009 9.985007e+02

1999 rows × 4 columns

df.plot()
<AxesSubplot:>

df.plot(subplots=True)
array([<AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>, <AxesSubplot:>],
      dtype=object)

df
$\sin$ log $\frac{sin(x)}{x}$ x^3
0.005 0.005000 -5.298317 0.999996 1.250000e-07
0.010 0.010000 -4.605170 0.999983 1.000000e-06
0.015 0.014999 -4.199705 0.999963 3.375000e-06
0.020 0.019999 -3.912023 0.999933 8.000000e-06
0.025 0.024997 -3.688879 0.999896 1.562500e-05
... ... ... ... ...
9.975 -0.522877 2.300082 -0.052419 9.925187e+02
9.980 -0.527132 2.300583 -0.052819 9.940120e+02
9.985 -0.531374 2.301084 -0.053217 9.955067e+02
9.990 -0.535603 2.301585 -0.053614 9.970030e+02
9.995 -0.539819 2.302085 -0.054009 9.985007e+02

1999 rows × 4 columns

((df - df.min(axis=0)) / (df.max(axis=0) - df.min(axis=0))).plot()
<AxesSubplot:>

df = pd.DataFrame(np.random.randn(1000, 5),
                  columns="a,b,c,d,e".split(","))
df
a b c d e
0 1.593274 0.568722 -0.114487 0.251630 -1.210856
1 -0.393734 0.085253 0.099422 -1.530616 0.327623
2 0.279196 -0.377051 0.004175 -1.483492 -1.479796
3 0.134687 -0.667723 -0.011556 0.839491 -0.173930
4 -2.810668 -0.150654 -0.481044 -0.234694 0.899731
... ... ... ... ... ...
995 -0.837658 1.315288 -0.364523 1.993571 1.584878
996 -2.104663 -2.553118 -1.242666 0.201987 -0.305332
997 -1.195587 -1.577903 0.849912 0.327590 -0.001670
998 -0.035563 -0.489252 1.930498 -0.262645 0.825932
999 -0.643267 -0.828981 -0.202735 -0.257866 0.070815

1000 rows × 5 columns

df = df.assign(f=df.sum(axis=1))
df
a b c d e f
0 1.593274 0.568722 -0.114487 0.251630 -1.210856 1.088284
1 -0.393734 0.085253 0.099422 -1.530616 0.327623 -1.412052
2 0.279196 -0.377051 0.004175 -1.483492 -1.479796 -3.056967
3 0.134687 -0.667723 -0.011556 0.839491 -0.173930 0.120969
4 -2.810668 -0.150654 -0.481044 -0.234694 0.899731 -2.777329
... ... ... ... ... ... ...
995 -0.837658 1.315288 -0.364523 1.993571 1.584878 3.691555
996 -2.104663 -2.553118 -1.242666 0.201987 -0.305332 -6.003792
997 -1.195587 -1.577903 0.849912 0.327590 -0.001670 -1.597657
998 -0.035563 -0.489252 1.930498 -0.262645 0.825932 1.968971
999 -0.643267 -0.828981 -0.202735 -0.257866 0.070815 -1.862035

1000 rows × 6 columns

df.query("f > 5")["f"].std()
0.6609257763922614
df.query("f <= 5")["f"].std()
2.1891090850524444
"tolist" in dir(df)
False
def search(obj, query):
    import re

    return list(filter(lambda x: re.search(query, x), dir(obj)))
search(a, "lis")
['tolist']
import numpy as np
import pandas as pd
def search(obj, query):
    import re
    from rich import inspect
    from rich.pretty import Pretty
    from rich.panel import Panel

    z = list(filter(lambda x: re.search(query, x), dir(obj)))
    p = Panel(Pretty("a"), title=f"Searching for `{query}`")
    # return p
    ps = []
    for q in z:
        ps.append(Panel(Pretty(inspect(getattr(obj, q), methods=True, docs=True))))
search(np.random, "normal")
╭─ <built-in method lognormal of numpy.random.mtrand.RandomState object at 0x10c627840> ─╮
 def RandomState.lognormal(...)                                                         
                                                                                        
 lognormal(mean=0.0, sigma=1.0, size=None)                                              
                                                                                        
 29 attribute(s) not shown. Run inspect(inspect) for options.                           
╰────────────────────────────────────────────────────────────────────────────────────────╯
╭─ <built-in method multivariate_normal of numpy.random.mtrand.RandomState object at 0x10c627840> ─╮
 def RandomState.multivariate_normal(...)                                                         
                                                                                                  
 multivariate_normal(mean, cov, size=None, check_valid='warn', tol=1e-8)                          
                                                                                                  
 29 attribute(s) not shown. Run inspect(inspect) for options.                                     
╰──────────────────────────────────────────────────────────────────────────────────────────────────╯
╭─ <built-in method normal of numpy.random.mtrand.RandomState object at 0x10c627840> ─╮
 def RandomState.normal(...)                                                         
                                                                                     
 normal(loc=0.0, scale=1.0, size=None)                                               
                                                                                     
 29 attribute(s) not shown. Run inspect(inspect) for options.                        
╰─────────────────────────────────────────────────────────────────────────────────────╯
╭─ <built-in method standard_normal of numpy.random.mtrand.RandomState object at 0x10c627840> ─╮
 def RandomState.standard_normal(...)                                                         
                                                                                              
 standard_normal(size=None)                                                                   
                                                                                              
 29 attribute(s) not shown. Run inspect(inspect) for options.                                 
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
getattr(pd.DataFrame().values, "tolist")
<function ndarray.tolist>
pd.DataFrame.to
df = pd.DataFrame()
search(df.values, "list")
╭───────────────────────────────────────────── Searching for {query} ─────────────────────────────────────────────╮
│ ['tolist']                                                                                                      │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
search(pd.DataFrame, "to")
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ [                                                                                                               │
│     '_constructor',                                                                                             │
│     '_constructor_sliced',                                                                                      │
│     '_to_dict_of_blocks',                                                                                       │
│     'kurtosis',                                                                                                 │
│     'to_clipboard',                                                                                             │
│     'to_csv',                                                                                                   │
│     'to_dict',                                                                                                  │
│     'to_excel',                                                                                                 │
│     'to_feather',                                                                                               │
│     'to_gbq',                                                                                                   │
│     'to_hdf',                                                                                                   │
│     'to_html',                                                                                                  │
│     'to_json',                                                                                                  │
│     'to_latex',                                                                                                 │
│     'to_markdown',                                                                                              │
│     'to_numpy',                                                                                                 │
│     'to_orc',                                                                                                   │
│     'to_parquet',                                                                                               │
│     'to_period',                                                                                                │
│     'to_pickle',                                                                                                │
│     'to_records',                                                                                               │
│     'to_sql',                                                                                                   │
│     'to_stata',                                                                                                 │
│     'to_string',                                                                                                │
│     'to_timestamp',                                                                                             │
│     'to_xarray',                                                                                                │
│     'to_xml'                                                                                                    │
│ ]                                                                                                               │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
q = "normal"
np.random.normal
<function RandomState.normal>
from rich import inspect

inspect(eval(f"np.random.{q}"), methods=True, all=True)
╭─────────────── <built-in method normal of numpy.random.mtrand.RandomState object at 0x10c627840> ───────────────╮
 def RandomState.normal(...)                                                                                     
                                                                                                                 
 normal(loc=0.0, scale=1.0, size=None)                                                                           
                                                                                                                 
            __doc__ = '\n        normal(loc=0.0, scale=1.0, size=None)\n\n        Draw random samples from a     
                      normal (Gaussian) distribution.\n\n        The probability density function of the normal  
                      distribution, first\n        derived by De Moivre and 200 years later by both Gauss and    
                      Laplace\n        independently [2]_, is often called the bell curve because of\n           
                      its characteristic shape (see the example below).\n\n        The normal distributions      
                      occurs often in nature.  For example, it\n        describes the commonly occurring         
                      distribution of samples influenced\n        by a large number of tiny, random              
                      disturbances, each with its own\n        unique distribution [2]_.\n\n        .. note::\n  
                      New code should use the ``normal`` method of a ``default_rng()``\n            instance     
                      instead; please see the :ref:`random-quick-start`.\n\n        Parameters\n                 
                      ----------\n        loc : float or array_like of floats\n            Mean ("centre") of    
                      the distribution.\n        scale : float or array_like of floats\n            Standard     
                      deviation (spread or "width") of the distribution. Must be\n            non-negative.\n    
                      size : int or tuple of ints, optional\n            Output shape.  If the given shape is,   
                      e.g., ``(m, n, k)``, then\n            ``m * n * k`` samples are drawn.  If size is        
                      ``None`` (default),\n            a single value is returned if ``loc`` and ``scale`` are   
                      both scalars.\n            Otherwise, ``np.broadcast(loc, scale).size`` samples are        
                      drawn.\n\n        Returns\n        -------\n        out : ndarray or scalar\n              
                      Drawn samples from the parameterized normal distribution.\n\n        See Also\n            
                      --------\n        scipy.stats.norm : probability density function, distribution or\n       
                      cumulative density function, etc.\n        random.Generator.normal: which should be used   
                      for new code.\n\n        Notes\n        -----\n        The probability density for the     
                      Gaussian distribution is\n\n        .. math:: p(x) = \\frac{1}{\\sqrt{ 2 \\pi \\sigma^2    
                      }}\n                         e^{ - \\frac{ (x - \\mu)^2 } {2 \\sigma^2} },\n\n             
                      where :math:`\\mu` is the mean and :math:`\\sigma` the standard\n        deviation. The    
                      square of the standard deviation, :math:`\\sigma^2`,\n        is called the variance.\n\n  
                      The function has its peak at the mean, and its "spread" increases with\n        the        
                      standard deviation (the function reaches 0.607 times its maximum at\n        :math:`x +    
                      \\sigma` and :math:`x - \\sigma` [2]_).  This implies that\n        normal is more likely  
                      to return samples lying close to the mean, rather\n        than those far away.\n\n        
                      References\n        ----------\n        .. [1] Wikipedia, "Normal distribution",\n         
                      https://en.wikipedia.org/wiki/Normal_distribution\n        .. [2] P. R. Peebles Jr.,       
                      "Central Limit Theorem" in "Probability,\n               Random Variables and Random       
                      Signal Principles", 4th ed., 2001,\n               pp. 51, 51, 125.\n\n        Examples\n  
                      --------\n        Draw samples from the distribution:\n\n        >>> mu, sigma = 0, 0.1 #  
                      mean and standard deviation\n        >>> s = np.random.normal(mu, sigma, 1000)\n\n         
                      Verify the mean and the variance:\n\n        >>> abs(mu - np.mean(s))\n        0.0  # may  
                      vary\n\n        >>> abs(sigma - np.std(s, ddof=1))\n        0.1  # may vary\n\n            
                      Display the histogram of the samples, along with\n        the probability density          
                      function:\n\n        >>> import matplotlib.pyplot as plt\n        >>> count, bins, ignored 
                      = plt.hist(s, 30, density=True)\n        >>> plt.plot(bins, 1/(sigma * np.sqrt(2 * np.pi)) 
                      *\n        ...                np.exp( - (bins - mu)**2 / (2 * sigma**2) ),\n        ...    
                      linewidth=2, color=\'r\')\n        >>> plt.show()\n\n        Two-by-four array of samples  
                      from N(3, 6.25):\n\n        >>> np.random.normal(3, 2.5, size=(2, 4))\n                    
                      array([[-4.49401501,  4.00950034, -1.81814867,  7.29718677],   # random\n               [  
                      0.39924804,  4.68456316,  4.99394529,  4.84057254]])  # random\n\n        '                
         __module__ = None                                                                                       
           __name__ = 'normal'                                                                                   
       __qualname__ = 'RandomState.normal'                                                                       
           __self__ = RandomState(MT19937) at 0x10C627840                                                        
 __text_signature__ = None                                                                                       
           __call__ = def __call__(*args, **kwargs): Call self as a function.                                    
          __class__ = class __class__():                                                                         
        __delattr__ = def __delattr__(name, /): Implement delattr(self, name).                                   
            __dir__ = def __dir__(): Default dir() implementation.                                               
             __eq__ = def __eq__(value, /): Return self==value.                                                  
         __format__ = def __format__(format_spec, /): Default object formatter.                                  
             __ge__ = def __ge__(value, /): Return self>=value.                                                  
   __getattribute__ = def __getattribute__(name, /): Return getattr(self, name).                                 
             __gt__ = def __gt__(value, /): Return self>value.                                                   
           __hash__ = def __hash__(): Return hash(self).                                                         
           __init__ = def __init__(*args, **kwargs): Initialize self.  See help(type(self)) for accurate         
                      signature.                                                                                 
  __init_subclass__ = def __init_subclass__(...) This method is called when a class is subclassed.               
             __le__ = def __le__(value, /): Return self<=value.                                                  
             __lt__ = def __lt__(value, /): Return self<value.                                                   
             __ne__ = def __ne__(value, /): Return self!=value.                                                  
            __new__ = def __new__(*args, **kwargs): Create and return a new object.  See help(type) for accurate 
                      signature.                                                                                 
         __reduce__ = def __reduce__(...) Helper for pickle.                                                     
      __reduce_ex__ = def __reduce_ex__(protocol, /): Helper for pickle.                                         
           __repr__ = def __repr__(): Return repr(self).                                                         
        __setattr__ = def __setattr__(name, value, /): Implement setattr(self, name, value).                     
         __sizeof__ = def __sizeof__(): Size of object in memory, in bytes.                                      
            __str__ = def __str__(): Return str(self).                                                           
   __subclasshook__ = def __subclasshook__(...) Abstract classes can override this to customize issubclass().    
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
inspect(inspect)
╭─────────────────────────────────────── <function inspect at 0x10f3fc550> ───────────────────────────────────────╮
 def inspect(obj: Any, *, console: Optional[ForwardRef('Console')] = None, title: Optional[str] = None, help:    
 bool = False, methods: bool = False, docs: bool = True, private: bool = False, dunder: bool = False, sort: bool 
 = True, all: bool = False, value: bool = True) -> None:                                                         
                                                                                                                 
 Inspect any Python object.                                                                                      
                                                                                                                 
 * inspect(<OBJECT>) to see summarized info.                                                                     
 * inspect(<OBJECT>, methods=True) to see methods.                                                               
 * inspect(<OBJECT>, help=True) to see full (non-abbreviated) help.                                              
 * inspect(<OBJECT>, private=True) to see private attributes (single underscore).                                
 * inspect(<OBJECT>, dunder=True) to see attributes beginning with double underscore.                            
 * inspect(<OBJECT>, all=True) to see all attributes.                                                            
                                                                                                                 
 Args:                                                                                                           
     obj (Any): An object to inspect.                                                                            
     title (str, optional): Title to display over inspect result, or None use type. Defaults to None.            
     help (bool, optional): Show full help text rather than just first paragraph. Defaults to False.             
     methods (bool, optional): Enable inspection of callables. Defaults to False.                                
     docs (bool, optional): Also render doc strings. Defaults to True.                                           
     private (bool, optional): Show private attributes (beginning with underscore). Defaults to False.           
     dunder (bool, optional): Show attributes starting with double underscore. Defaults to False.                
     sort (bool, optional): Sort attributes alphabetically. Defaults to True.                                    
     all (bool, optional): Show all attributes. Defaults to False.                                               
     value (bool, optional): Pretty print value. Defaults to True.                                               
                                                                                                                 
 35 attribute(s) not shown. Run inspect(inspect) for options.                                                    
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
from dataclasses import dataclass


@dataclass
class InventoryItem:
    """Class for keeping track of an item in inventory."""

    name: str
    unit_price: float
    quantity_on_hand: int = 0
c = InventoryItem("A", 20)
c
InventoryItem(name='A', unit_price=20, quantity_on_hand=0)
class InventoryItemOld:
    def __init__(self, name: str, unit_price: float, 
                 quantity_on_hand: int = 0):
        self.name = name
        self.unit_price = unit_price
        self.quantity_on_hand = quantity_on_hand
d = InventoryItemOld("A", 2)
d
<__main__.InventoryItemOld at 0x166340430>
def greeting(name: str) -> str:
    return 'Hello ' + name
greeting("Abc")
'Hello Abc'
greeting(10)
TypeError: can only concatenate str (not "int") to str