From 8dc1d37d702894196c833bc5ef1a041c0f817120 Mon Sep 17 00:00:00 2001 From: Jesse Perla Date: Wed, 12 Jul 2023 13:15:49 -0700 Subject: [PATCH 1/9] Examples of creating classes --- lectures/scientific/randomness.md | 102 ++++++++++++++++++++++++++++++ 1 file changed, 102 insertions(+) diff --git a/lectures/scientific/randomness.md b/lectures/scientific/randomness.md index 419375da..3529d8a3 100644 --- a/lectures/scientific/randomness.md +++ b/lectures/scientific/randomness.md @@ -272,6 +272,108 @@ element at a time. For more information see the [QuantEcon lecture on performance Python](https://python-programming.quantecon.org/numba.html) code. + +### Aside: Using Class to Hold Parameters + +We have been using objects and classes both internal to python (e.g. `list`) from external libraries (e.g. `numpy.array`). Sometimes it is convenient to create your own classes to organize parameter, data, and functions. + +In this section we will reimplement our function using new classes to hold parameters. + +First, we rewrite `simulate_loan_repayments` so that instead of a collection of individual parameters, it takes in an object (titles `params`). + +```{code-cell} python +def simulate_loan_repayments_2(N, params): + # Extract fields from params object + r = params.r + repayment_part = params.repayment_part + repayment_full = params.repayment_full + + random_numbers = np.random.rand(N) + + # start as 0 -- no repayment + repayment_sims = np.zeros(N) + + # adjust for full and partial repayment + partial = random_numbers <= 0.20 + repayment_sims[partial] = repayment_part + + full = ~partial & (random_numbers <= 0.95) + repayment_sims[full] = repayment_full + + repayment_sims = (1 / (1 + r)) * repayment_sims + + return repayment_sims +``` + +Any object which fulfills `params.r, params.replayment_part` and `params.repayment_full` will work, so we will create a few versions of this to explore features of custom classes in Python. + +The most important function in a class is the `__init__` function which determines how it is constructed and creates an object of that type. This function has the special argument `self` which refers to the new object being created, and with which you can easily add new fields. For example, + +```{code-cell} python +class LoanRepaymentParams: + # A special function 'constructor' + def __init__(self, r, repayment_full, repayment_part): + self.r = r + self.repayment_full = repayment_full + self.repayment_part = repayment_part + +# Create an instance of the class +params = LoanRepaymentParams(0.05, 50_000.0, 25_000) +print(params.r) +``` + +The inside of the `__init__` function simply takes the arguments and assigns them as new fields in the `self`. Calling the `LoanRepaymentParams(...)` implicitly calls the `__init__` function and returns the new object. + +We can then use the new object to call the function `simulate_loan_repayments_2` as before. + +```{code-cell} python +N = 1000 +params = LoanRepaymentParams(0.05, 50_000.0, 25_000) +print(np.mean(simulate_loan_repayments_2(N, params))) +``` + +One benefit of using a class is that you can do calculations in the constructor. For example, instead of passing in the partial repayment amount, we could pass in the fraction of the full repayment that is paid. + +```{code-cell} python +class LoanRepaymentParams2: + def __init__(self, r, repayment_full, partial_fraction = 0.3): + self.r = r + self.repayment_full = repayment_full + + # This does a calculation and sets a new value + self.repayment_part = repayment_full * partial_fraction +# Create an instance of the class +params = LoanRepaymentParams2(0.05, 50_000.0, 0.5) +print(params.repayment_part) # Acccess the calculation +print(np.mean(simulate_loan_repayments_2(N, params))) +``` + +This setup a default value for the `partial_fraction` so that we could also have called this with `LoanRepaymentParams2(0.05, 50_000)`. + + +FInally, there are some special features we can use to create classes in python which automatically create the `__init__` function, allow for more easily setting default values. The easiest is to create a `dataclass` (see [documentation](https://docs.python.org/3/library/dataclasses.html)). + +```{code-cell} python +from dataclasses import dataclass +@dataclass +class LoanRepaymentParams3: + r = 0.05 + repayment_full = 50_000 + repayment_part = 25_000 + +params = LoanRepaymentParams3() # uses all defaults +params2 = LoanRepaymentParams3(repayment_full = 60_000) + +# show the objects +print(params) +print(params2) + +# simulate using the new object +print(np.mean(simulate_loan_repayments_2(N, params2))) +``` + +The `@dataclass` is an example of a python decorator (see [documentation](https://docs.python.org/3/glossary.html#term-decorator)). Decorators take in a class (or function) and return a new class (or function) with some additional features. In this case, it automatically creates the `__init__` function, allows for default values, and adds a new `__repr__` function which determines how the object is printed. + #### Profitability Threshold Rather than looking for the break even point, we might be interested in the largest loan size that From 05ec9e8adc0a5f07cdb0d8973dffa6d803f9789e Mon Sep 17 00:00:00 2001 From: Jesse Perla Date: Wed, 12 Jul 2023 13:56:15 -0700 Subject: [PATCH 2/9] Added in placeholder for the functions on classes --- lectures/python_fundamentals/functions.md | 52 +++++++++++++++++++++++ lectures/scientific/randomness.md | 2 + 2 files changed, 54 insertions(+) diff --git a/lectures/python_fundamentals/functions.md b/lectures/python_fundamentals/functions.md index c0c460c0..56ff500c 100644 --- a/lectures/python_fundamentals/functions.md +++ b/lectures/python_fundamentals/functions.md @@ -582,6 +582,58 @@ In that example, the `z` on the left hand side of `z = z` refers to the local variable name in the function whereas the `z` on the right hand side refers to the `z` in the outer scope. + +### Creating Custom Types + +IN PROGRESS! + +- we aer used to doing `x = dict("a": 1, "b": 2)` and then `x["a"]` to access + +Used both internal and etemrianl + +We can create new one + +```{code-cell} python +class A + def __init__(self, x, y): + self.x = x + self.y = y +``` + +Explain the self and the special `__init__` + +Exlain the difference of an instance/object vs. the class + +```{code-cell} python +a = A(1, 2) +b = A(3, 4) +# Notice that these are different objects +a == b +``` + +Tell people how to see the `type` +Point at the debugger to see the `a.x` etc. fields + +Show a method + +```{code-cell} python +class B + def __init__(self, x, y): + self.x = x + self.y = y + + def add(self): + return self.x + self.y +``` + +```{code-cell} python +a = B(1, 2) +print a.add() +``` + + + + ### Aside: Methods As we learned earlier, all variables in Python have a type associated diff --git a/lectures/scientific/randomness.md b/lectures/scientific/randomness.md index 3529d8a3..11d5ad34 100644 --- a/lectures/scientific/randomness.md +++ b/lectures/scientific/randomness.md @@ -342,6 +342,7 @@ class LoanRepaymentParams2: # This does a calculation and sets a new value self.repayment_part = repayment_full * partial_fraction + # Create an instance of the class params = LoanRepaymentParams2(0.05, 50_000.0, 0.5) print(params.repayment_part) # Acccess the calculation @@ -355,6 +356,7 @@ FInally, there are some special features we can use to create classes in python ```{code-cell} python from dataclasses import dataclass + @dataclass class LoanRepaymentParams3: r = 0.05 From ac89f3ec5a488feb29d37e6b4673c29701a88a94 Mon Sep 17 00:00:00 2001 From: Phil Solimine <15682144+doctor-phil@users.noreply.github.com> Date: Thu, 13 Jul 2023 17:09:12 -0700 Subject: [PATCH 3/9] Update randomness.md --- lectures/scientific/randomness.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lectures/scientific/randomness.md b/lectures/scientific/randomness.md index 11d5ad34..7132414a 100644 --- a/lectures/scientific/randomness.md +++ b/lectures/scientific/randomness.md @@ -352,7 +352,7 @@ print(np.mean(simulate_loan_repayments_2(N, params))) This setup a default value for the `partial_fraction` so that we could also have called this with `LoanRepaymentParams2(0.05, 50_000)`. -FInally, there are some special features we can use to create classes in python which automatically create the `__init__` function, allow for more easily setting default values. The easiest is to create a `dataclass` (see [documentation](https://docs.python.org/3/library/dataclasses.html)). +Finally, there are some special features we can use to create classes in python which automatically create the `__init__` function, allow for more easily setting default values. The easiest is to create a `dataclass` (see [documentation](https://docs.python.org/3/library/dataclasses.html)). ```{code-cell} python from dataclasses import dataclass @@ -364,7 +364,7 @@ class LoanRepaymentParams3: repayment_part = 25_000 params = LoanRepaymentParams3() # uses all defaults -params2 = LoanRepaymentParams3(repayment_full = 60_000) +params2 = LoanRepaymentParams3(0.05, 60_000, 25_000) # changes the full repayment amount # show the objects print(params) From be10361f0706abe44f8c19666525f05e6e46fbef Mon Sep 17 00:00:00 2001 From: Phil Solimine <15682144+doctor-phil@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:09:57 -0700 Subject: [PATCH 4/9] Update functions.md --- lectures/python_fundamentals/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lectures/python_fundamentals/functions.md b/lectures/python_fundamentals/functions.md index 56ff500c..77fb8213 100644 --- a/lectures/python_fundamentals/functions.md +++ b/lectures/python_fundamentals/functions.md @@ -594,7 +594,7 @@ Used both internal and etemrianl We can create new one ```{code-cell} python -class A +class A: def __init__(self, x, y): self.x = x self.y = y From f6a2da3c0641b45ae6efc9c7aa2441768ada8778 Mon Sep 17 00:00:00 2001 From: Phil Solimine <15682144+doctor-phil@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:15:46 -0700 Subject: [PATCH 5/9] Update randomness.md --- lectures/scientific/randomness.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lectures/scientific/randomness.md b/lectures/scientific/randomness.md index 7132414a..56f33268 100644 --- a/lectures/scientific/randomness.md +++ b/lectures/scientific/randomness.md @@ -359,12 +359,12 @@ from dataclasses import dataclass @dataclass class LoanRepaymentParams3: - r = 0.05 - repayment_full = 50_000 - repayment_part = 25_000 + r: float = 0.05 + repayment_full: float = 50_000 + repayment_part: float = 25_000 params = LoanRepaymentParams3() # uses all defaults -params2 = LoanRepaymentParams3(0.05, 60_000, 25_000) # changes the full repayment amount +params2 = LoanRepaymentParams3(repayment_full= 60_000) # changes the full repayment amount # show the objects print(params) From 1230e9083cce2eb11e634565c429af5c3d5ee7ff Mon Sep 17 00:00:00 2001 From: Phil Solimine <15682144+doctor-phil@users.noreply.github.com> Date: Thu, 13 Jul 2023 18:41:58 -0700 Subject: [PATCH 6/9] fix bug with class def --- lectures/python_fundamentals/functions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lectures/python_fundamentals/functions.md b/lectures/python_fundamentals/functions.md index 77fb8213..a10de44b 100644 --- a/lectures/python_fundamentals/functions.md +++ b/lectures/python_fundamentals/functions.md @@ -617,7 +617,7 @@ Point at the debugger to see the `a.x` etc. fields Show a method ```{code-cell} python -class B +class B: def __init__(self, x, y): self.x = x self.y = y From 0a055dbb16d1a7e8044cefcaeed4dc889ed4c61f Mon Sep 17 00:00:00 2001 From: Phil Solimine <15682144+doctor-phil@users.noreply.github.com> Date: Thu, 13 Jul 2023 20:14:55 -0700 Subject: [PATCH 7/9] Update functions.md --- lectures/python_fundamentals/functions.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lectures/python_fundamentals/functions.md b/lectures/python_fundamentals/functions.md index a10de44b..2e8f224e 100644 --- a/lectures/python_fundamentals/functions.md +++ b/lectures/python_fundamentals/functions.md @@ -627,8 +627,8 @@ class B: ``` ```{code-cell} python -a = B(1, 2) -print a.add() +b = B(1, 2) +print(b.add()) ``` From d195c6582ade43c89c3d1bfd44e20ccc1ccd6f29 Mon Sep 17 00:00:00 2001 From: Phil Solimine <15682144+doctor-phil@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:30:14 -0700 Subject: [PATCH 8/9] Update functions.md --- lectures/python_fundamentals/functions.md | 135 ++++++++++++++-------- 1 file changed, 85 insertions(+), 50 deletions(-) diff --git a/lectures/python_fundamentals/functions.md b/lectures/python_fundamentals/functions.md index 2e8f224e..caaf126c 100644 --- a/lectures/python_fundamentals/functions.md +++ b/lectures/python_fundamentals/functions.md @@ -583,56 +583,6 @@ to the local variable name in the function whereas the `z` on the right hand side refers to the `z` in the outer scope. -### Creating Custom Types - -IN PROGRESS! - -- we aer used to doing `x = dict("a": 1, "b": 2)` and then `x["a"]` to access - -Used both internal and etemrianl - -We can create new one - -```{code-cell} python -class A: - def __init__(self, x, y): - self.x = x - self.y = y -``` - -Explain the self and the special `__init__` - -Exlain the difference of an instance/object vs. the class - -```{code-cell} python -a = A(1, 2) -b = A(3, 4) -# Notice that these are different objects -a == b -``` - -Tell people how to see the `type` -Point at the debugger to see the `a.x` etc. fields - -Show a method - -```{code-cell} python -class B: - def __init__(self, x, y): - self.x = x - self.y = y - - def add(self): - return self.x + self.y -``` - -```{code-cell} python -b = B(1, 2) -print(b.add()) -``` - - - ### Aside: Methods @@ -677,6 +627,75 @@ s.upper() s.title() ``` + +### Creating Custom Types + +Python allows for Object-Oriented Programming (OOP), allowing you to define your own custom types and merge together some sets of parameters with custom methods. This can help you streamline your code by making it more modular. + +We are used to defining variables like `x = dict("a": 1, "b": 2)` and then using notation like `x["a"]` to access the value of `1`. We can also define our own custom types and use them in similar ways. + +For example, a simple class that stores two variables would look like this: + +```{code-cell} python +class A: + def __init__(self, x, y): + self.x = x + self.y = y +``` + +Used both internal and external to classes, the `__init__` method is a special method that is called when an object is created. It is used to initialize the object's state. The `self` argument refers to the object itself. The `self` argument is always the first argument of any method in a class. The `self` argument is not passed in when the method is called, but Python will pass in the object itself when the method is called. + +A class, defined by the `class` keyword, is a blueprint for an object. It defines the attributes and methods that an object will have. An object is an instance of a class that has been created and assigned to a variable. It is created by calling the class name as if it were a function. When you call the class name, the object is created and the `__init__` method is called by default. + +```{code-cell} python +a = A(1, 2) +b = A(3, 4) +# Notice that these are different objects +a == b +``` + +Tell people how to see the `type`: +You can see that `a` and `b` are both instances of the `A` class by using the `type` function. + +```{code-cell} python +type(a) +``` +Point at the debugger to see the `a.x` etc. fields +You can access the attributes of an object using the dot notation. For example, to access the `x` attribute of the `a` object, you would use `a.x`. + +```{code-cell} python +print(f"a.x = {a.x} and a.y = {a.y}") +``` + +In addition to attributes, objects can also have methods. Methods are functions that are defined inside of a class. They are accessed using the dot notation as well. For example, let's define a method that adds the `x` and `y` attributes of an object. + + +```{code-cell} python +class B: + def __init__(self, x, y): + self.x = x + self.y = y + + def add(self): + return self.x + self.y +``` + +We can now create an object of type `B` and call the `add` method, in the same way that we called methods on built-in types (like the `.upper()` method on a string.) + +```{code-cell} python +b = B(1, 2) +print(b.add()) +``` + +Using custom classes can often be a helpful way to organize your code and make it more modular, by grouping together related variables and functions. Understanding how to create and use custom classes is also a key part of understanding how Python works under the hood, and can be crucial to using some of the more advanced Python packages (like [PyTorch](https://pytorch.org/tutorials/beginner/basics/quickstart_tutorial.html).) + +````{admonition} Exercise +:name: dir2-4-5 + +See exercise 5 in the {ref}`exercise list `. +```` + + ## More on Scope (Optional) Keep in mind that with mathematical functions, the arguments are just dummy names @@ -841,3 +860,19 @@ These can *only* be set by name. ``` ({ref}`back to text `) + +### Exercise 5 + +Define a custom class called `CobbDouglas` that collects the parameters `z` and `alpha` as attributes, and has a method called `produce` that takes `K` and `L` as arguments and returns the output from the Cobb-Douglas production function. + +```{code-cell} python +# Your code here. +``` + +Now create an instance of the `CobbDouglas` class called `cobb_douglas1` with `z = 1` and `alpha = 0.33`. Use the `produce` method to compute the output when `K = 1` and `L = 0.5`. + +```{code-cell} python +# Your code here. +``` + +({ref}`back to text `) From a7a940c24ebecb57e874b64b6d8a31837c306189 Mon Sep 17 00:00:00 2001 From: Phil Solimine <15682144+doctor-phil@users.noreply.github.com> Date: Wed, 19 Jul 2023 12:41:56 -0700 Subject: [PATCH 9/9] updates to objects/class material --- lectures/python_fundamentals/functions.md | 1 - lectures/scientific/randomness.md | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lectures/python_fundamentals/functions.md b/lectures/python_fundamentals/functions.md index caaf126c..9400b5f0 100644 --- a/lectures/python_fundamentals/functions.md +++ b/lectures/python_fundamentals/functions.md @@ -654,7 +654,6 @@ b = A(3, 4) a == b ``` -Tell people how to see the `type`: You can see that `a` and `b` are both instances of the `A` class by using the `type` function. ```{code-cell} python diff --git a/lectures/scientific/randomness.md b/lectures/scientific/randomness.md index 56f33268..cc5172a9 100644 --- a/lectures/scientific/randomness.md +++ b/lectures/scientific/randomness.md @@ -275,7 +275,7 @@ For more information see the ### Aside: Using Class to Hold Parameters -We have been using objects and classes both internal to python (e.g. `list`) from external libraries (e.g. `numpy.array`). Sometimes it is convenient to create your own classes to organize parameter, data, and functions. +We have been using objects and classes both internal to python (e.g. `list`) from external libraries (e.g. `numpy.array`). Sometimes it is convenient to create your own classes to organize parameter, data, and functions. In this section we will reimplement our function using new classes to hold parameters. @@ -374,7 +374,7 @@ print(params2) print(np.mean(simulate_loan_repayments_2(N, params2))) ``` -The `@dataclass` is an example of a python decorator (see [documentation](https://docs.python.org/3/glossary.html#term-decorator)). Decorators take in a class (or function) and return a new class (or function) with some additional features. In this case, it automatically creates the `__init__` function, allows for default values, and adds a new `__repr__` function which determines how the object is printed. +The `@dataclass` is an example of a python decorator (see [documentation](https://docs.python.org/3/glossary.html#term-decorator)). Decorators take in a class (or function) and return a new class (or function) with some additional features. In this case, it automatically creates the `__init__` function, allows for default values, and adds a new `__repr__` function which determines how the object is printed. #### Profitability Threshold