Amy Hanlon - Investigating Python Wats

Amy Hanlon - Investigating Python Wats

Many of us have experienced a "wat" in Python - some behavior that totally mystifies us. We'll look at three areas where wats arise - identity, mutability, and scope. For each of these three topics, we'll look at some common surprising behaviors, investigate the cause of the behaviors, and cover some practical tips on how to avoid related bugs.

https://us.pycon.org/2015/schedule/presentation/384/

D5710b3bca38f1233274b4cbc523dc4b?s=128

PyCon 2015

April 18, 2015
Tweet

Transcript

  1. Investigating Python Wats

  2. Venmo Hacker School Recurse Center ! @amygdalama mathamy.com

  3. WAT

  4. Identity Mutability Scope

  5. Identity

  6. >>>  a  =  256   >>>  b  =  256  

    >>>  a  is  b   ???
  7. >>>  a  =  256   >>>  b  =  256  

    >>>  a  is  b   True
  8. >>>  a  =  257   >>>  b  =  257  

    >>>  a  is  b   ???
  9. >>>  a  =  257   >>>  b  =  257  

    >>>  a  is  b   False
  10. $  python   ! ! -­‐5 … 255 256

  11. -­‐5 … 255 256 a >>>  a  =  256  

    ! !
  12. b a >>>  a  =  256   >>>  b  =

     256   ! -­‐5 … 255 256
  13. b a >>>  a  =  256   >>>  b  =

     256   >>>  a  is  b   True -­‐5 … 255 256
  14. a … 255 256 … 257 >>>  a  =  257

      ! !
  15. a … 255 256 … 257 257 b >>>  a

     =  257   >>>  b  =  257   !
  16. a … 255 256 … 257 257 b >>>  a

     =  257   >>>  b  =  257   >>>  a  is  b   False
  17. >>>  a  =  257;  b  =  257   >>>  a

     is  b   ???
  18. >>>  a  =  257;  b  =  257   >>>  a

     is  b   True
  19. >>>  a  =  257   >>>  b  =  257  

    >>>  a  is  b   False   ! >>>  a  =  257;  b  =  257   >>>  a  is  b   True
  20. >>>  a  =  257   >>>   ! ! !

    ! !
  21. >>>  a  =  257   >>>  source  =  "a  =

     257"   >>>  code_obj_a  =  compile(   ...          source=source,   ...          filename="",   ...          mode="exec")   !
  22. >>>  a  =  257   >>>  source  =  "a  =

     257"   >>>  code_obj_a  =  compile(   ...          source=source,   ...          filename="",   ...          mode="exec")   >>>  code_obj_a.co_consts   (257,  None)
  23. >>>  source  =  "b  =  257"   >>>  code_obj_b  =

     compile(   ...          source=source,   ...          filename="",   ...          mode="exec")   !
  24. >>>  source  =  "b  =  257"   >>>  code_obj_b  =

     compile(   ...          source=source,   ...          filename="",   ...          mode="exec")   >>>  code_obj_b.co_consts   (257,  None)
  25. >>>  source  =  "a  =  257;  b  =  257"  

    ! ! ! ! !
  26. >>>  source  =  "a  =  257;  b  =  257"  

    >>>  code_obj  =  compile(     ...          source=source,   ...          filename="",   ...          mode="exec")   !
  27. >>>  source  =  "a  =  257;  b  =  257"  

    >>>  code_obj  =  compile(     ...          source=source,   ...          filename="",   ...          mode="exec")   >>>  code_obj.co_consts   (257,  None)
  28. Mutability

  29. >>>  row  =  [""]  *  3   >>>  row  

    ['',  '',  '']   ! ! ! !
  30. >>>  row  =  [""]  *  3   >>>  row  

    ['',  '',  '']   >>>  board  =  [row]  *  3   ! ! !
  31. >>>  row  =  [""]  *  3   >>>  row  

    ['',  '',  '']   >>>  board  =  [row]  *  3   >>>  board   [['',  '',  ''],    ['',  '',  ''],    ['',  '',  '']]
  32. >>>  board[0]   ['',  '',  '']   !

  33. >>>  board[0]   ['',  '',  '']   >>>  board[0][0]  

    ''
  34. >>>  board[0][0]  =  "X"   ! ! !

  35. >>>  board[0][0]  =  "X"   >>>  board   ???  

    !
  36. >>>  board[0][0]  =  "X"   >>>  board   [['X',  '',

     ''],    ['X',  '',  ''],    ['X',  '',  '']]
  37. row "" "" "" >>>  row  =  [""]  *  3

  38. >>>  board  =  [row]  *  3 row board[0] board[1] board[2]

    "" "" ""
  39. >>>  board[0][0]  =  "X" row board[0] board[1] board[2] "X" ""

    ""
  40. Mutable Default Arguments

  41. def  append_cat(l=[]):   ! ! ! ! ! !

  42. def  append_cat(l=[]):          l.append('cat')      

       return  l   ! ! ! !
  43. def  append_cat(l=[]):          l.append(‘cat’)      

       return  l   ! >>>  append_cat()   ???   !
  44. def  append_cat(l=[]):          l.append(‘cat’)      

       return  l   ! >>>  append_cat()   ['cat']   !
  45. def  append_cat(l=[]):          l.append(‘cat’)      

       return  l   ! >>>  append_cat()   ['cat']   >>>  append_cat()   ???
  46. def  append_cat(l=[]):          l.append(‘cat’)      

       return  l   ! >>>  append_cat()   ['cat']   >>>  append_cat()   ['cat',  'cat']
  47. def  append_cat(l=[]):          l.append(‘cat’)      

       return  l   ! >>>  append_cat.__defaults__   ???   !
  48. def  append_cat(l=[]):          l.append(‘cat’)      

       return  l   ! >>>  append_cat.__defaults__   ([],)   !
  49. def  append_cat(l=[]):          l.append('cat')      

       return  l   ! >>>  append_cat()   >>>  append_cat.__defaults__   ???  
  50. def  append_cat(l=[]):          l.append('cat')      

       return  l   ! >>>  append_cat()   >>>  append_cat.__defaults__   (['cat'],)  
  51. >>>  append_cat.__defaults__   (['cat'],)   ! !

  52. >>>  append_cat.__defaults__   (['cat'],)   >>>  _[0].append('dragon')   !

  53. >>>  append_cat.__defaults__   (['cat'],)   >>>  _[0].append('dragon')   >>>  append_cat()

      ???
  54. >>>  append_cat.__defaults__   (['cat'],)   >>>  _[0].append('dragon')   >>>  append_cat()

      ['cat',  'dragon',  'cat']
  55. Scope

  56. >>>  a  =  1   ! ! ! !

  57. >>>  a  =  1   >>>  def  foo():   ...

             return  a   ! !
  58. >>>  a  =  1   >>>  def  foo():   ...

             return  a   ! >>>  foo()   ???
  59. >>>  a  =  1   >>>  def  foo():   ...

             return  a   ! >>>  foo()   1
  60. >>>  a  =  1   >>>  def  foo():   ...

             return  a   >>>  foo()   ???
  61. • locals()          #  {}   !

    ! >>>  a  =  1   >>>  def  foo():   ...          return  a   >>>  foo()   ???
  62. • locals()          #  {}   !

    ! >>>  a  =  1   >>>  def  foo():   ...          return  a   >>>  foo()   ???
  63. • locals()          #  {}   •

    enclosing        #  {}   ! >>>  a  =  1   >>>  def  foo():   ...          return  a   >>>  foo()   ???
  64. • locals()          #  {}   •

    enclosing        #  {}   ! >>>  a  =  1   >>>  def  foo():   ...          return  a   >>>  foo()   ???
  65. • locals()          #  {}   •

    enclosing        #  {}   • globals()        #  {'a':  1}   >>>  a  =  1   >>>  def  foo():   ...          return  a   >>>  foo()   ???
  66. • locals()          #  {}   •

    enclosing        #  {}   ✓ globals()        #  {'a':  1}   >>>  a  =  1   >>>  def  foo():   ...          return  a   >>>  foo()   1
  67. • locals()          #  {}   •

    enclosing        #  {}   ✓ globals()        #  {'a':  1}   • builtins          #  {'True':  True,…} >>>  a  =  1   >>>  def  foo():   ...          return  a   >>>  foo()   1
  68. >>>  a  =  1   >>>  def  foo():   ...

             return  a   ! >>>  from  dis  import  dis   ! !
  69. >>>  a  =  1   >>>  def  foo():   ...

             return  a   ! >>>  from  dis  import  dis   >>>  dis(foo)      2              0  LOAD_GLOBAL                    0  (a)                        3  RETURN_VALUE      
  70. >>>  a  =  1   >>>  def  foo():   ...

             a  +=  1   ...          return  a   ! >>>  foo()   ???
  71. >>>  a  =  1   >>>  def  foo():   ...

             a  +=  1   ...          return  a   ! >>>  foo()   UnboundLocalError:  local   variable  'a'  referenced   before  assignment
  72. “When you make an assignment to a variable in a

    scope, that variable becomes local to that scope.”
  73. >>>  a  =  1   >>>  def  foo():   ...

             #  a  +=  1   ...          a  =  a  +  1   ...          return  a
  74. >>>  a  =  1   >>>  def  foo():   ...

             #  a  +=  1   ...          a  =  a  +  1   ...          return  a
  75. >>>  a  =  1   >>>  def  foo():   ...

             #  a  +=  1   ...          a  =  a  +  1   ...          return  a
  76. >>>  a  =  1   >>>  def  foo():   ...

             a  +=  1   ...          return  a   ...     >>>  dis(foo)      2              0  LOAD_FAST                        0  (a)                        3  LOAD_CONST                      1  (1)                        6  INPLACE_ADD                                          7  STORE_FAST                      0  (a)     !    3            10  LOAD_FAST                        0  (a)                      13  RETURN_VALUE            
  77. >>>  a  =  1   >>>  def  foo():   ...

             a  +=  1   ...          return  a   ...     >>>  dis(foo)      2              0  LOAD_FAST                        0  (a)                        3  LOAD_CONST                      1  (1)                        6  INPLACE_ADD                                          7  STORE_FAST                      0  (a)     !    3            10  LOAD_FAST                        0  (a)                      13  RETURN_VALUE            
  78. “Knowledge is power — it’s measured in wats.” ! -

    Rose Ames
  79. Thank you! @amygdalama mathamy.com

  80. Links • https://www.destroyallsoftware.com/talks/wat • http://akaptur.github.io/blog/2013/10/29/a-python-puzzle/ • https://docs.python.org/3.4/c-api/long.html • https://docs.python.org/3/reference/ compound_stmts.html#function-definitions

    • http://effbot.org/zone/default-values.htm • https://docs.python.org/3/reference/ executionmodel.html#naming-and-binding • http://eli.thegreenplace.net/2011/05/15/understanding- unboundlocalerror-in-python/ • http://rose.github.io/posts/measured-in-wats/