Methods

Calvin (Deutschbein)

W10Fri: 01 Nov

Announcements

  • Project 3 next Monday.
    • Milestone 4a by Thursday
    • Milestone 4b tonight
    • Milestone 4c by Saturday
  • Midterm looming. Stay tuned.

Today

  • Methods
    • Variables / Attributes
    • Functions / Methods
    • Dot notation
    • Getters / Setters
    • Dunder Methods

Editorializing

  • My minority take: Methods are bad
    • Methods bundle data and computing non-obviously. "".join("hi","world") # this works join("hi","world") # this doesn't "".len("hi") # this doesn't len("hi") # this does
    • Methods make tracking data confusing xs = [1,2,3] x = xs.sort()[0] # error
    • I refer to this as "the Java problem" but it impacts all modern languages.

Editorializing

  • ctrl+f "Appendix: Power"
  • We note that Python now has the features it lacked in 2002.

Editorializing

  • Takeaways
    • Finding methods confusing is valid.
    • Finding methods tedious is valid.
    • Think critically about what you learn / are taught!
    • Future trends are ambigious
      • I expect: more functions, fewer classes.
  • R is the other kind of language - functional. Read more.
  • Python... pretty much works as a functional language now too.

Today

  • Methods
    • ✓ Editorializing
    • Variables / Attributes
    • Functions / Methods
    • Dot notation
    • Getters / Setters
    • Dunder Methods

Variables / Attributes

  • A variable is a value that isn't tied to an object. x = 7
  • An attribute is a value that is tied to an object. class Box: def __init__(self): self.x = 7

Variables / Attributes

  • We recall we can set a variable equal to anything... like a function. class Box: def __init__(self): self.x = 7 self.make_pos = abs # "absolute value"
  • We can test this code: b = Box() print( b.x, abs(-2.5), b.make_pos(-1.5) )
  • We see: 7 2.5 1.5

Variables / Attributes

  • To make it easier to see what's happening, a break a print into multiple lines.
  • This code was auto-indented by VS Code, but I would use the same style manually.
  • It's an easy way to see what 3 things I'm printing, or to add comments! b = Box() print( b.x, # should be 7 abs(-2.5), # should be 2.5 b.make_pos(-1.5) # should be 1.5 )
  • We see: 7 2.5 1.5

Variables / Attributes

  • Just like numbers or strings, an attribute can be a function. class Box: def __init__(self): self.x = 7 self.make_pos = abs # "absolute value"
  • We can also define them outside of init, like so: class Box: x = 7 make_pos = abs # "absolute value"
  • This code behaves identically.
  • The usefulness of init is just to allow us to change what e.g. 7 is.

Today

  • Methods
    • ✓ Editorializing
    • ✓ Variables / Attributes
    • Functions / Methods
    • Dot notation
    • Getters / Setters
    • Dunder Methods

Functions / Methods

  • When we define a function inside of a class, it is a method - a special kind of function.
  • There's one wrinkle though. class Box: x = 7 def print_x(): print(self.x) b = Box() b.print_x() # this errors
  • We get an error: TypeError: Box.print_x() takes 0 positional arguments but 1 was given
  • What does that mean?

Functions / Methods

  • We can get the same kind of error - a type error - by using a function incorrectly.
  • abs(-1,-2)
  • We get an error: TypeError: abs() takes exactly one argument (2 given)
  • When making an integer positive, we only accept one integer. But what about 'print_x'?

Functions / Methods

  • We wrote 'print_x' to need no argument, and we provide no argument, yet Python encounters an error. class Box: x = 7 def print_x(): print(self.x)
  • The hint is with how we wrote '__init__' def __init__(self): # what is self? pass
  • Functions used as methods always have a "secret" argument: "self".

Functions / Methods

  • When we define a function inside of a class, it is a method - a special kind of function.
  • There's one wrinkle though. class Box: x = 7 def print_x(self): # add self print(x) b = Box() b.print_x() # this works
  • We get an error: 7
  • The purpose of this is to allow methods to find variables defined inside the class (like 'x'/'self.x') but not defined inside the method.

Today

  • Methods
    • ✓ Editorializing
    • ✓ Variables / Attributes
    • ✓ Functions / Methods
    • Dot notation
    • Getters / Setters
    • Dunder Methods

Dot notation

  • Remember this? pixels = image.get_pixel_array() print(pixels[0][0]) print(GImage.get_red(pixels[0][0]))
  • Look here: GImage.get_red(pixels[0][0])
  • Um.... GImage is not a variable, that's the name of a class.
  • Call this dot notation - we can use methods both from:
    • A class, like 'int' or 'box'
    • An instances of a class, like '7' or 'b = Box(7)'

Dot notation

  • Back to 'print_x'
  • There's one wrinkle though. class Box: # no x here def print_7(): # back to no self print(7) Box.print_7()
  • Doing this, we can't use variables inside box
    • The class has no self, just like 'int' has no numerical value)
  • But we can run snippets of code that don't use anything in the object. TypeError: Box.print_x() takes 0 positional arguments but 1 was given
  • I try to avoid using this because I find it confusing.

Today

  • Methods
    • ✓ Editorializing
    • ✓ Variables / Attributes
    • ✓ Functions / Methods
    • ✓ Dot notation
    • Getters / Setters
    • Dunder Methods

Getters / Setters

  • Willamette CS151 aggressively uses getters and setters.
  • Let's look at example, with box. class Box: def __init__(self, data): self.val = data
  • We could perform the following operations. >>> b = Box(13) >>> b.val 13 >>> b.val = 17 >>> b.val 17

Getters / Setters

  • By convention, some attributes' names are prefixed with "_" single underscore.
  • It is "bad form" to work with these values directly.
    • The reason it is bad form and arguments against this claim are discussed in the early 00s blog posts I've been sharing.
    • I say: Out of scope. In this class, we accept as axiomatic it is bad form.
    • This, by contrast is good form Nicki Minaj - Good Form ft. Lil Wayne
    class Box: def __init__(self, data): self._val = data
  • We can, but "shouldn't" do the following. >>> b = Box(13) >>> b._val # bad form, but allowed 13
  • Please don't do that in a job interview.

Getters / Setters

  • By convention, some attributes' names are prefixed with "_" single underscore.
  • We use methods instead class Box: def __init__(self, data): self._val = data def get_val(self): return self._val def set_val(self, data): self._val = data

Getters / Setters

  • We use methods
    class Box: def __init__(self, data): self._val = data def get_val(self): return self._val def set_val(self, data): self._val = data >>> b = Box(13) >>> b._val # bad/wrong 13 >>> b._val = 17 # bad/wrong >>> b._val # bad/wrong 17

Getters / Setters

  • We use methods
    class Box: def __init__(self, data): self._val = data def get_val(self): return self._val def set_val(self, data): self._val = data >>> b = Box(13) >>> b.get_val() 13 >>> b.set_val(17) >>> b.get_val() 17

Getters / Setters

  • Pros: Can enforce, e.g. having a int
    class IntBox: def __init__(self, data): if type(data) == int: self._val = data else: self._val = 0 def get_val(self): return self._val def set_val(self, data): if type(data) == int: self._val = data >>> b = IntBox("hi") >>> b.get_val() 0 >>> b.set_val(7) >>> b.get_val() 7 >>> b.set_val(print) >>> b.get_val() 7

Getters / Setters

  • Cons: Can't use the assignment ops
    class IntBox: def __init__(self, data): if type(data) == int: self._val = data else: self._val = 0 def get_val(self): return self._val def set_val(self, data): if type(data) == int: self._val = data >>> b = IntBox(7) >>> b._val *= 3 # bad form >>> b.get_val() 21 >>> b.set_val(b.get_val() // 3) >>> b.get_val() 7
  • Updating values gets clunky real fast.

Today

  • Methods
    • ✓ Editorializing
    • ✓ Variables / Attributes
    • ✓ Functions / Methods
    • ✓ Dot notation
    • ✓ Getters / Setters
    • Dunder Methods

Dunder Methods

  • Dunder for "double underscore"
  • We've seen one, __init__
  • Here's the thing: Printing box is super ugly. >>> b <test.Box object at 0x0000025528D1E9F0>
  • There's a lot of names for dunder methods that have special meanings.
  • __repr__ determines how something is "represented"
    • It must return a string.

Dunder Methods

  • Dunder for "double underscore"
  • Here's __repr__ class Box: def __init__(self, data): self.val = data def __repr__(self): return "Box(" + str(self.val) + ")"
  • Here's what it looks like: >>> b = Box(7) >>> b Box(7) >>> b.val **= 2 >>> b Box(49)

Dunder Methods

  • Some others:
    __str__Determines what is returned when applying 'str()' to the object
    __abs__Determines what is returned when applying 'abs()' to the object
    __and__Determines what is returned when object 'and' object is calculated
    __lt__Determines what is returned when object '<' object is calculated
  • I use __lt__ a lot because I tend to want to be able to sort things.
  • I never write a class without at least __init__, __repr__, and __str__, but usually __repr__ and __str__ are the same.
  • I usually don't use the others. There's a full list you can find pretty easily (not linking because you should practice finding it if you need it.)

Dunder Methods

  • By the way, ints have these two... >>> x = 7 >>> type(7) <class 'int'>>>> x.__add__(4) 11
  • Quite a bit of Python runs on dunder methods, just "secretly".

Today

  • ✓ Methods
    • Variables / Attributes
    • Functions / Methods
    • Dot notation
    • Getters / Setters
    • Dunder Methods

Announcements

  • Project 3 next Monday.
    • Milestone 4a by Thursday
    • Milestone 4b tonight
    • Milestone 4c by Saturday
  • Midterm looming. Stay tuned.