├── .devcontainer └── devcontainer.json ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── main.yml ├── .gitignore ├── .vscode └── settings.json ├── CONTRIBUTING.md ├── Finished ├── Ch 1 │ ├── challenge.py │ ├── class_finished.py │ ├── definition_finished.py │ ├── instance_finished.py │ └── typecheck_finished.py ├── Ch 2 │ ├── abstract_finished.py │ ├── challenge.py │ ├── composition_finished.py │ ├── inheritance_finished.py │ ├── interface_finished.py │ └── multiple_finished.py ├── Ch 3 │ ├── challenge.py │ ├── magicattr_finished.py │ ├── magiccall_finished.py │ ├── magiceq_finished.py │ └── magicstr_finished.py └── Ch 4 │ ├── challenge.py │ ├── dataclass_finished.py │ ├── datadefault_finished.py │ ├── immutable_finished.py │ └── postinit_finished.py ├── LICENSE ├── NOTICE ├── README.md └── Start ├── Ch 1 ├── challenge.py ├── class_start.py ├── definition_start.py ├── instance_start.py └── typecheck_start.py ├── Ch 2 ├── abstract_start.py ├── challenge.py ├── composition_start.py ├── inheritance_start.py ├── interface_start.py └── multiple_start.py ├── Ch 3 ├── challenge.py ├── magicattr_start.py ├── magiccall_start.py ├── magiceq_start.py └── magicstr_start.py └── Ch 4 ├── challenge.py ├── dataclass_start.py ├── datadefault_start.py ├── immutable_start.py └── postinit_start.py /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensions": [ 3 | "GitHub.github-vscode-theme" 4 | // Additional Extensions Here 5 | ], 6 | "onCreateCommand": "echo PS1='\"$ \"' >> ~/.bashrc", 7 | "postAttachCommand": "git pull --all" 8 | 9 | // Update welcome text and set terminal prompt to '$ ' 10 | } 11 | // DevContainer Reference: https://code.visualstudio.com/docs/remote/devcontainerjson-reference 12 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # Codeowners for these exercise files: 2 | # * (asterisk) deotes "all files and folders" 3 | # Example: * @producer @instructor 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 7 | 8 | ## Issue Overview 9 | 10 | 11 | ## Describe your environment 12 | 13 | 14 | ## Steps to Reproduce 15 | 16 | 1. 17 | 2. 18 | 3. 19 | 4. 20 | 21 | ## Expected Behavior 22 | 23 | 24 | ## Current Behavior 25 | 26 | 27 | ## Possible Solution 28 | 29 | 30 | ## Screenshots / Video 31 | 32 | 33 | ## Related Issues 34 | 35 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Copy To Branches 2 | on: 3 | workflow_dispatch: 4 | jobs: 5 | copy-to-branches: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | with: 10 | fetch-depth: 0 11 | - name: Copy To Branches Action 12 | uses: planetoftheweb/copy-to-branches@v1.2 13 | env: 14 | key: main 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Apple 2 | *.DS_Store 3 | 4 | # Built application files 5 | *.apk 6 | *.ap_ 7 | 8 | # Files for the Dalvik VM 9 | *.dex 10 | 11 | # Java class files 12 | *.class 13 | 14 | # Generated files 15 | bin/ 16 | gen/ 17 | 18 | # Gradle files 19 | .gradle/ 20 | build/ 21 | 22 | # Local configuration file (sdk path, etc) 23 | local.properties 24 | 25 | # Proguard folder generated by Eclipse 26 | proguard/ 27 | 28 | # Log Files 29 | *.log 30 | 31 | # Android Studio Navigation editor temp files 32 | .navigation/ 33 | 34 | # Android Studio captures folder 35 | captures/ 36 | 37 | # Google Doc files 38 | *.gsheet 39 | *.gslides 40 | *.gdoc 41 | 42 | # MS Office files 43 | *.xls_ 44 | *.doc_ 45 | *.ppt_ 46 | 47 | # PDF files 48 | *.pdf 49 | 50 | # ZIP files 51 | *.zip 52 | 53 | # VISUAL STUDIO FILES 54 | 55 | # User-specific files 56 | *.suo 57 | *.user 58 | *.userosscache 59 | *.sln.docstates 60 | 61 | # User-specific files (MonoDevelop/Xamarin Studio) 62 | *.userprefs 63 | 64 | # Build results 65 | [Dd]ebug/ 66 | [Dd]ebugPublic/ 67 | [Rr]elease/ 68 | [Rr]eleases/ 69 | x64/ 70 | x86/ 71 | bld/ 72 | [Bb]in/ 73 | [Oo]bj/ 74 | [Ll]og/ 75 | __pycache__/ 76 | 77 | # Visual Studio 2015 cache/options directory 78 | .vs/ 79 | # Uncomment if you have tasks that create the project's static files in wwwroot 80 | #wwwroot/ 81 | 82 | # MSTest test Results 83 | [Tt]est[Rr]esult*/ 84 | [Bb]uild[Ll]og.* 85 | 86 | # NUNIT 87 | *.VisualState.xml 88 | TestResult.xml 89 | 90 | # Build Results of an ATL Project 91 | [Dd]ebugPS/ 92 | [Rr]eleasePS/ 93 | dlldata.c 94 | 95 | # DNX 96 | project.lock.json 97 | artifacts/ 98 | 99 | *_i.c 100 | *_p.c 101 | *_i.h 102 | *.ilk 103 | *.meta 104 | *.obj 105 | *.pch 106 | *.pdb 107 | *.pgc 108 | *.pgd 109 | *.rsp 110 | *.sbr 111 | *.tlb 112 | *.tli 113 | *.tlh 114 | *.tmp 115 | *.tmp_proj 116 | *.log 117 | *.vspscc 118 | *.vssscc 119 | .builds 120 | *.pidb 121 | *.svclog 122 | *.scc 123 | 124 | # Chutzpah Test files 125 | _Chutzpah* 126 | 127 | # Visual C++ cache files 128 | ipch/ 129 | *.aps 130 | *.ncb 131 | *.opendb 132 | *.opensdf 133 | *.sdf 134 | *.cachefile 135 | *.VC.db 136 | *.VC.VC.opendb 137 | 138 | # Visual Studio profiler 139 | *.psess 140 | *.vsp 141 | *.vspx 142 | *.sap 143 | 144 | # TFS 2012 Local Workspace 145 | $tf/ 146 | 147 | # Guidance Automation Toolkit 148 | *.gpState 149 | 150 | # ReSharper is a .NET coding add-in 151 | _ReSharper*/ 152 | *.[Rr]e[Ss]harper 153 | *.DotSettings.user 154 | 155 | # JustCode is a .NET coding add-in 156 | .JustCode 157 | 158 | # TeamCity is a build add-in 159 | _TeamCity* 160 | 161 | # DotCover is a Code Coverage Tool 162 | *.dotCover 163 | 164 | # NCrunch 165 | _NCrunch_* 166 | .*crunch*.local.xml 167 | nCrunchTemp_* 168 | 169 | # MightyMoose 170 | *.mm.* 171 | AutoTest.Net/ 172 | 173 | # Web workbench (sass) 174 | .sass-cache/ 175 | 176 | # Installshield output folder 177 | [Ee]xpress/ 178 | 179 | # DocProject is a documentation generator add-in 180 | DocProject/buildhelp/ 181 | DocProject/Help/*.HxT 182 | DocProject/Help/*.HxC 183 | DocProject/Help/*.hhc 184 | DocProject/Help/*.hhk 185 | DocProject/Help/*.hhp 186 | DocProject/Help/Html2 187 | DocProject/Help/html 188 | 189 | # Click-Once directory 190 | publish/ 191 | 192 | # Publish Web Output 193 | *.[Pp]ublish.xml 194 | *.azurePubxml 195 | # TODO: Comment the next line if you want to checkin your web deploy settings 196 | # but database connection strings (with potential passwords) will be unencrypted 197 | *.pubxml 198 | *.publishproj 199 | 200 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 201 | # checkin your Azure Web App publish settings, but sensitive information contained 202 | # in these scripts will be unencrypted 203 | PublishScripts/ 204 | 205 | # NuGet Packages 206 | *.nupkg 207 | # The packages folder can be ignored because of Package Restore 208 | **/packages/* 209 | # except build/, which is used as an MSBuild target. 210 | !**/packages/build/ 211 | # Uncomment if necessary however generally it will be regenerated when needed 212 | #!**/packages/repositories.config 213 | # NuGet v3's project.json files produces more ignoreable files 214 | *.nuget.props 215 | *.nuget.targets 216 | 217 | # Microsoft Azure Build Output 218 | csx/ 219 | *.build.csdef 220 | 221 | # Microsoft Azure Emulator 222 | ecf/ 223 | rcf/ 224 | 225 | # Windows Store app package directories and files 226 | AppPackages/ 227 | BundleArtifacts/ 228 | Package.StoreAssociation.xml 229 | _pkginfo.txt 230 | 231 | # Visual Studio cache files 232 | # files ending in .cache can be ignored 233 | *.[Cc]ache 234 | # but keep track of directories ending in .cache 235 | !*.[Cc]ache/ 236 | 237 | # Others 238 | ClientBin/ 239 | ~$* 240 | *~ 241 | *.dbmdl 242 | *.dbproj.schemaview 243 | *.pfx 244 | *.publishsettings 245 | node_modules/ 246 | orleans.codegen.cs 247 | 248 | # Since there are multiple workflows, uncomment next line to ignore bower_components 249 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 250 | #bower_components/ 251 | 252 | # RIA/Silverlight projects 253 | Generated_Code/ 254 | 255 | # Backup & report files from converting an old project file 256 | # to a newer Visual Studio version. Backup files are not needed, 257 | # because we have git ;-) 258 | _UpgradeReport_Files/ 259 | Backup*/ 260 | UpgradeLog*.XML 261 | UpgradeLog*.htm 262 | 263 | # SQL Server files 264 | *.mdf 265 | *.ldf 266 | 267 | # Business Intelligence projects 268 | *.rdl.data 269 | *.bim.layout 270 | *.bim_*.settings 271 | 272 | # Microsoft Fakes 273 | FakesAssemblies/ 274 | 275 | # GhostDoc plugin setting file 276 | *.GhostDoc.xml 277 | 278 | # Node.js Tools for Visual Studio 279 | .ntvs_analysis.dat 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio LightSwitch build output 288 | **/*.HTMLClient/GeneratedArtifacts 289 | **/*.DesktopClient/GeneratedArtifacts 290 | **/*.DesktopClient/ModelManifest.xml 291 | **/*.Server/GeneratedArtifacts 292 | **/*.Server/ModelManifest.xml 293 | _Pvt_Extensions 294 | 295 | # Paket dependency manager 296 | .paket/paket.exe 297 | paket-files/ 298 | 299 | # FAKE - F# Make 300 | .fake/ 301 | 302 | # JetBrains Rider 303 | .idea/ 304 | *.sln.iml 305 | 306 | # VS Code folder 307 | .vscode/ 308 | 309 | # Databricks file 310 | *.pyi 311 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.bracketPairColorization.enabled": true, 3 | "editor.cursorBlinking": "solid", 4 | "editor.fontFamily": "ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono', 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro', 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace", 5 | "editor.fontLigatures": false, 6 | "editor.fontSize": 22, 7 | "editor.formatOnPaste": true, 8 | "editor.formatOnSave": true, 9 | "editor.lineNumbers": "on", 10 | "editor.matchBrackets": "always", 11 | "editor.minimap.enabled": false, 12 | "editor.smoothScrolling": true, 13 | "editor.tabSize": 2, 14 | "editor.useTabStops": true, 15 | "emmet.triggerExpansionOnTab": true, 16 | "explorer.openEditors.visible": 0, 17 | "files.autoSave": "afterDelay", 18 | "screencastMode.onlyKeyboardShortcuts": true, 19 | "terminal.integrated.fontSize": 18, 20 | "workbench.activityBar.visible": true, 21 | "workbench.colorTheme": "Default Light Modern", 22 | "workbench.fontAliasing": "antialiased", 23 | "workbench.statusBar.visible": true, 24 | "python.linting.enabled": false 25 | } -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | 2 | Contribution Agreement 3 | ====================== 4 | 5 | This repository does not accept pull requests (PRs). All pull requests will be closed. 6 | 7 | However, if any contributions (through pull requests, issues, feedback or otherwise) are provided, as a contributor, you represent that the code you submit is your original work or that of your employer (in which case you represent you have the right to bind your employer). By submitting code (or otherwise providing feedback), you (and, if applicable, your employer) are licensing the submitted code (and/or feedback) to LinkedIn and the open source community subject to the BSD 2-Clause license. 8 | -------------------------------------------------------------------------------- /Finished/Ch 1/challenge.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Programming challenge: define a class to represent a stock symbol 3 | 4 | 5 | class Stock: 6 | def __init__(self, ticker, price, company) -> None: 7 | self.ticker = ticker 8 | self.price = price 9 | self.company = company 10 | 11 | def get_description(self): 12 | return f"{self.ticker}: {self.company} -- ${self.price}" 13 | 14 | # ~~~~~~~~~ TEST CODE ~~~~~~~~~ 15 | msft = Stock("MSFT", 342.0, "Microsoft Corp") 16 | goog = Stock("GOOG", 135.0, "Google Inc") 17 | meta = Stock("META", 275.0, "Meta Platforms Inc") 18 | amzn = Stock("AMZN", 135.0, "Amazon Inc") 19 | 20 | print(msft.get_description()) 21 | print(goog.get_description()) 22 | print(meta.get_description()) 23 | print(amzn.get_description()) 24 | -------------------------------------------------------------------------------- /Finished/Ch 1/class_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using class-level and static methods 3 | 4 | 5 | class Book: 6 | # TODO: Properties defined at the class level are shared by all instances 7 | BOOK_TYPES = ("HARDCOVER", "PAPERBACK", "EBOOK") 8 | # TODO: double-underscore properties are hidden from other classes 9 | __booklist = None 10 | 11 | # static methods do not receive class or instance arguments 12 | # and usually operate on data that is not instance-specific 13 | @staticmethod 14 | def get_booklist(): 15 | if Book.__booklist == None: 16 | Book.__booklist = [] 17 | return Book.__booklist 18 | 19 | # class methods receive a class as their argument and can only 20 | # operate on class-level data 21 | @classmethod 22 | def get_book_types(cls): 23 | return cls.BOOK_TYPES 24 | 25 | # instance methods receive a specific object instance as an argument 26 | # and operate on data specific to that object instance 27 | def set_title(self, newtitle): 28 | self.title = newtitle 29 | 30 | def __init__(self, title, booktype): 31 | self.title = title 32 | if (not booktype in self.BOOK_TYPES): 33 | raise ValueError(f"{booktype} is not a valid book type") 34 | else: 35 | self.booktype = booktype 36 | 37 | 38 | # TODO: access the class attribute 39 | print("Book types: ", Book.get_book_types()) 40 | 41 | # TODO: Create some book instances 42 | b1 = Book("Title 1", "HARDCOVER") 43 | b2 = Book("Title 2", "PAPERBACK") 44 | 45 | # TODO: Use the static method to access a singleton object 46 | thebooks = Book.get_booklist() 47 | thebooks.append(b1) 48 | thebooks.append(b2) 49 | print(thebooks) 50 | -------------------------------------------------------------------------------- /Finished/Ch 1/definition_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Basic class definitions 3 | 4 | 5 | class Book: 6 | # the "init" function is called when the instance is 7 | # created and ready to be initialized 8 | def __init__(self, title): 9 | self.title = title 10 | 11 | 12 | # TODO: create instances of the class 13 | book1 = Book("Brave New World") 14 | book2 = Book("War and Peace") 15 | 16 | # TODO: print the class and property 17 | print(book1) 18 | print(book1.title) 19 | -------------------------------------------------------------------------------- /Finished/Ch 1/instance_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using instance methods and attributes 3 | 4 | 5 | class Book: 6 | # the "init" function is called when the instance is 7 | # created and ready to be initialized 8 | def __init__(self, title, pages, author, price): 9 | self.title = title 10 | self.pages = pages 11 | self.author = author 12 | self.price = price 13 | self.__secret = "This is a secret attribute" 14 | 15 | # instance methods are defined like any other function, with the 16 | # first argument as the object ("self" is just a convention) 17 | def set_discount(self, amount): 18 | self._discount = amount 19 | 20 | def get_price(self): 21 | if hasattr(self, "_discount"): 22 | return self.price - (self.price * self._discount) 23 | else: 24 | return self.price 25 | 26 | 27 | # create some book instances 28 | b1 = Book("War and Peace", "Leo Tolstoy", 1225, 39.95) 29 | b2 = Book("The Catcher in the Rye", "JD Salinger", 234, 29.95) 30 | 31 | # print the price of book1 32 | print(b1.get_price()) 33 | 34 | # try setting the discount 35 | print(b2.get_price()) 36 | b2.set_discount(0.25) 37 | print(b2.get_price()) 38 | 39 | # properties with double underscores are hidden by the interpreter 40 | print(b2._discount) 41 | # print(b2.__secret) 42 | # print(b2._Book__secret) 43 | -------------------------------------------------------------------------------- /Finished/Ch 1/typecheck_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Checking class types and instances 3 | 4 | 5 | class Book: 6 | def __init__(self, title): 7 | self.title = title 8 | 9 | 10 | class Newspaper: 11 | def __init__(self, name): 12 | self.name = name 13 | 14 | 15 | # Create some instances of the classes 16 | b1 = Book("The Catcher In The Rye") 17 | b2 = Book("The Grapes of Wrath") 18 | n1 = Newspaper("The Washington Post") 19 | n2 = Newspaper("The New York Times") 20 | 21 | # TODO: use type() to inspect the object type 22 | print(type(b1)) 23 | print(type(n1)) 24 | 25 | # TODO: compare two types together 26 | print(type(b1) == type(b2)) 27 | print(type(b1) == type(n2)) 28 | 29 | # TODO: use isinstance to compare a specific instance to a known type 30 | print(isinstance(b1, Book)) 31 | print(isinstance(n1, Newspaper)) 32 | 33 | print(isinstance(n2, Book)) 34 | print(isinstance(n2, object)) 35 | -------------------------------------------------------------------------------- /Finished/Ch 2/abstract_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using Abstract Base Classes to enforce class constraints 3 | 4 | from abc import ABC, abstractmethod 5 | 6 | 7 | class GraphicShape(ABC): 8 | # Inheriting from ABC indicates that this is an abstract base class 9 | def __init__(self): 10 | super().__init__() 11 | 12 | # declaring a method as abstract requires a subclass to implement it 13 | @abstractmethod 14 | def calc_area(self): 15 | pass 16 | 17 | 18 | class Circle(GraphicShape): 19 | def __init__(self, radius): 20 | self.radius = radius 21 | 22 | def calc_area(self): 23 | return 3.14 * (self.radius ** 2) 24 | 25 | 26 | class Square(GraphicShape): 27 | def __init__(self, side): 28 | self.side = side 29 | 30 | def calc_area(self): 31 | return self.side * self.side 32 | 33 | 34 | # Abstract classes can't be instantiated themselves 35 | # g = GraphicShape() # this will error 36 | 37 | c = Circle(10) 38 | print(c.calc_area()) 39 | s = Square(12) 40 | print(s.calc_area()) 41 | -------------------------------------------------------------------------------- /Finished/Ch 2/challenge.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Programming challenge: use inheritance and abstract classes 3 | 4 | from abc import ABC, abstractmethod 5 | 6 | class Asset(ABC): 7 | def __init__(self, price): 8 | self.price = price 9 | 10 | @abstractmethod 11 | def get_description(self): 12 | pass 13 | 14 | class Stock(Asset): 15 | def __init__(self, ticker, price, company): 16 | super().__init__(price) 17 | self.company = company 18 | self.ticker = ticker 19 | 20 | def get_description(self): 21 | return f"{self.ticker}: {self.company} -- ${self.price}" 22 | 23 | class Bond(Asset): 24 | def __init__(self, price, description, duration, yieldamt): 25 | super().__init__(price) 26 | self.description = description 27 | self.duration = duration 28 | self.yieldamt = yieldamt 29 | 30 | def get_description(self): 31 | return f"{self.description}: {self.duration}yr : ${self.price} : {self.yieldamt}%" 32 | 33 | 34 | # ~~~~~~~~~ TEST CODE ~~~~~~~~~ 35 | try: 36 | ast = Asset(100.0) 37 | except: 38 | print("Can't instantiate Asset!") 39 | 40 | msft = Stock("MSFT", 342.0, "Microsoft Corp") 41 | goog = Stock("GOOG", 135.0, "Google Inc") 42 | meta = Stock("META", 275.0, "Meta Platforms Inc") 43 | amzn = Stock("AMZN", 135.0, "Amazon Inc") 44 | 45 | us30yr = Bond(95.31, "30 Year US Treasury", 30, 4.38) 46 | us10yr = Bond(96.70, "10 Year US Treasury", 10, 4.28) 47 | us5yr = Bond(98.65, "5 Year US Treasury", 5, 4.43) 48 | us2yr = Bond(99.57, "2 Year US Treasury", 2, 4.98) 49 | 50 | print(msft.get_description()) 51 | print(goog.get_description()) 52 | print(meta.get_description()) 53 | print(amzn.get_description()) 54 | 55 | print(us30yr.get_description()) 56 | print(us10yr.get_description()) 57 | print(us5yr.get_description()) 58 | print(us2yr.get_description()) 59 | -------------------------------------------------------------------------------- /Finished/Ch 2/composition_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using composition to build complex objects 3 | 4 | 5 | class Book: 6 | def __init__(self, title, price, author=None): 7 | self.title = title 8 | self.price = price 9 | 10 | # Use references to other objects, like author and chapters 11 | self.author = author 12 | self.chapters = [] 13 | 14 | def addchapter(self, chapter): 15 | self.chapters.append(chapter) 16 | 17 | def getbookpagecount(self): 18 | result = 0 19 | for ch in self.chapters: 20 | result += ch.pagecount 21 | return result 22 | 23 | 24 | class Author: 25 | def __init__(self, fname, lname): 26 | self.fname = fname 27 | self.lname = lname 28 | 29 | def __str__(self): 30 | return f"{self.fname} {self.lname}" 31 | 32 | 33 | class Chapter: 34 | def __init__(self, name, pagecount): 35 | self.name = name 36 | self.pagecount = pagecount 37 | 38 | 39 | auth = Author("Leo", "Tolstoy") 40 | b1 = Book("War and Peace", 39.95, auth) 41 | 42 | b1.addchapter(Chapter("Chapter 1", 104)) 43 | b1.addchapter(Chapter("Chapter 2", 89)) 44 | b1.addchapter(Chapter("Chapter 3", 124)) 45 | 46 | print(b1.title) 47 | print(b1.author) 48 | print(b1.getbookpagecount()) 49 | -------------------------------------------------------------------------------- /Finished/Ch 2/inheritance_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Understanding class inheritance 3 | 4 | 5 | class Publication: 6 | def __init__(self, title, price): 7 | self.title = title 8 | self.price = price 9 | 10 | 11 | class Periodical(Publication): 12 | def __init__(self, title, price, publisher, period): 13 | super().__init__(title, price) 14 | self.period = period 15 | self.publisher = publisher 16 | 17 | 18 | class Book(Publication): 19 | def __init__(self, title, author, pages, price): 20 | super().__init__(title, price) 21 | self.author = author 22 | self.pages = pages 23 | 24 | 25 | class Magazine(Periodical): 26 | def __init__(self, title, publisher, price, period): 27 | super().__init__(title, price, publisher, period) 28 | 29 | 30 | class Newspaper(Periodical): 31 | def __init__(self, title, publisher, price, period): 32 | super().__init__(title, price, publisher, period) 33 | 34 | 35 | b1 = Book("Brave New World", "Aldous Huxley", 311, 29.0) 36 | n1 = Newspaper("NY Times", "New York Times Company", 6.0, "Daily") 37 | m1 = Magazine("Scientific American", "Springer Nature", 5.99, "Monthly") 38 | 39 | print(b1.author) 40 | print(n1.publisher) 41 | print(b1.price, m1.price, n1.price) 42 | -------------------------------------------------------------------------------- /Finished/Ch 2/interface_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using Abstract Base Classes to implement interfaces 3 | 4 | from abc import ABC, abstractmethod 5 | 6 | 7 | class GraphicShape(ABC): 8 | def __init__(self): 9 | super().__init__() 10 | 11 | @abstractmethod 12 | def calcArea(self): 13 | pass 14 | 15 | 16 | class JSONify(ABC): 17 | @abstractmethod 18 | def toJSON(self): 19 | pass 20 | 21 | 22 | class Circle(GraphicShape, JSONify): 23 | def __init__(self, radius): 24 | self.radius = radius 25 | 26 | def calcArea(self): 27 | return 3.14 * (self.radius ** 2) 28 | 29 | def toJSON(self): 30 | return f"{{ \"square\": {str(self.calcArea())} }}" 31 | 32 | 33 | c = Circle(10) 34 | print(c.calcArea()) 35 | print(c.toJSON()) 36 | -------------------------------------------------------------------------------- /Finished/Ch 2/multiple_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Understanding multiple inheritance 3 | 4 | 5 | class A: 6 | def __init__(self): 7 | super().__init__() 8 | self.prop1 = "prop1" 9 | self.name = "Class A" 10 | 11 | 12 | class B: 13 | def __init__(self): 14 | super().__init__() 15 | self.prop2 = "prop2" 16 | self.name = "Class B" 17 | 18 | 19 | class C(B, A): 20 | def __init__(self): 21 | super().__init__() 22 | 23 | def showprops(self): 24 | print(self.prop1) 25 | print(self.prop2) 26 | print(self.name) 27 | 28 | 29 | # create the class and call showprops() 30 | c = C() 31 | print(C.__mro__) 32 | c.showprops() 33 | -------------------------------------------------------------------------------- /Finished/Ch 3/challenge.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Programming challenge: add methods for comparison and equality 3 | 4 | from abc import ABC, abstractmethod 5 | 6 | 7 | class Asset(ABC): 8 | def __init__(self, price): 9 | self.price = price 10 | 11 | @abstractmethod 12 | def __str__(self): 13 | pass 14 | 15 | 16 | class Stock(Asset): 17 | def __init__(self, ticker, price, company): 18 | super().__init__(price) 19 | self.company = company 20 | self.ticker = ticker 21 | 22 | def __str__(self): 23 | return f"{self.ticker}: {self.company} -- ${self.price}" 24 | 25 | def __lt__(self, other): 26 | return self.price < other.price 27 | 28 | 29 | class Bond(Asset): 30 | def __init__(self, price, description, duration, yieldamt): 31 | super().__init__(price) 32 | self.description = description 33 | self.duration = duration 34 | self.yieldamt = yieldamt 35 | 36 | def __str__(self): 37 | return f"{self.description}: {self.duration}yr : ${self.price} : {self.yieldamt}%" 38 | 39 | def __lt__(self, other): 40 | return self.yieldamt < other.yieldamt 41 | 42 | 43 | # ~~~~~~~~~ TEST CODE ~~~~~~~~~ 44 | stocks = [ 45 | Stock("MSFT", 342.0, "Microsoft Corp"), 46 | Stock("GOOG", 135.0, "Google Inc"), 47 | Stock("META", 275.0, "Meta Platforms Inc"), 48 | Stock("AMZN", 120.0, "Amazon Inc") 49 | ] 50 | 51 | bonds = [ 52 | Bond(95.31, "30 Year US Treasury", 30, 4.38), 53 | Bond(96.70, "10 Year US Treasury", 10, 4.28), 54 | Bond(98.65, "5 Year US Treasury", 5, 4.43), 55 | Bond(99.57, "2 Year US Treasury", 2, 4.98) 56 | ] 57 | 58 | stocks.sort() 59 | bonds.sort() 60 | 61 | for stock in stocks: 62 | print(stock) 63 | print("-----------") 64 | for bond in bonds: 65 | print(bond) 66 | -------------------------------------------------------------------------------- /Finished/Ch 3/magicattr_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the __str__ and __repr__ magic methods 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, price): 7 | super().__init__() 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | self._discount = 0.1 12 | 13 | # The __str__ function is used to return a user-friendly string 14 | # representation of the object 15 | def __str__(self): 16 | return f"{self.title} by {self.author}, costs {self.price}" 17 | 18 | # Called when an attribute is retrieved. Be aware that you can't 19 | # directly access the attr name otherwise a recursive loop is created 20 | def __getattribute__(self, name): 21 | if (name == "price"): 22 | p = super().__getattribute__("price") 23 | d = super().__getattribute__("_discount") 24 | return p - (p * d) 25 | return super().__getattribute__(name) 26 | 27 | # __setattr__ called when an attribute value is set. Don't set the attr 28 | # directly here otherwise a recursive loop causes a crash 29 | def __setattr__(self, name, value): 30 | if (name == "price"): 31 | if type(value) is not float: 32 | raise ValueError("The 'price' attribute must be a float") 33 | return super().__setattr__(name, value) 34 | 35 | # __getattr__ called when __getattribute__ lookup fails - you can 36 | # pretty much generate attributes on the fly with this method 37 | def __getattr__(self, name): 38 | return name + " is not here!" 39 | 40 | 41 | b1 = Book("War and Peace", "Leo Tolstoy", 39.95) 42 | b2 = Book("The Catcher in the Rye", "JD Salinger", 29.95) 43 | 44 | # Try setting and accessing the price 45 | b1.price = 38.95 46 | print(b1) 47 | 48 | b2.price = float(40) # using an int will raise an exception 49 | print(b2) 50 | 51 | # If an attribute doesn't exist, __getattr__ will be called 52 | print(b1.randomprop) 53 | -------------------------------------------------------------------------------- /Finished/Ch 3/magiccall_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the __str__ and __repr__ magic methods 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, price): 7 | super().__init__() 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | 12 | def __str__(self): 13 | return f"{self.title} by {self.author}, costs {self.price}" 14 | 15 | # TODO: the __call__ method can be used to call the object like a function 16 | def __call__(self, title, author, price): 17 | self.title = title 18 | self.author = author 19 | self.price = price 20 | 21 | 22 | b1 = Book("War and Peace", "Leo Tolstoy", 39.95) 23 | b2 = Book("The Catcher in the Rye", "JD Salinger", 29.95) 24 | 25 | # call the object as if it were a function 26 | print(b1) 27 | b1("Anna Karenina", "Leo Tolstoy", 49.95) 28 | print(b1) 29 | -------------------------------------------------------------------------------- /Finished/Ch 3/magiceq_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the __str__ and __repr__ magic methods 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, price): 7 | super().__init__() 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | 12 | # the __eq__ method checks for equality between two objects 13 | def __eq__(self, value): 14 | if not isinstance(value, Book): 15 | raise ValueError("Can't compare book to non-book type") 16 | 17 | return (self.title == value.title and 18 | self.author == value.author and 19 | self.price == value.price) 20 | 21 | # the __ge__ establishes >= relationship with another obj 22 | def __ge__(self, value): 23 | if not isinstance(value, Book): 24 | raise ValueError("Can't compare book to non-book type") 25 | 26 | return self.price >= value.price 27 | 28 | # the __lt__ establishes <= relationship with another obj 29 | def __lt__(self, value): 30 | if not isinstance(value, Book): 31 | raise ValueError("Can't compare book to non-book type") 32 | 33 | return self.price < value.price 34 | 35 | 36 | b1 = Book("War and Peace", "Leo Tolstoy", 39.95) 37 | b2 = Book("The Catcher in the Rye", "JD Salinger", 29.95) 38 | b3 = Book("War and Peace", "Leo Tolstoy", 39.95) 39 | b4 = Book("To Kill a Mockingbird", "Harper Lee", 24.95) 40 | 41 | # Check for equality 42 | print(b1 == b3) 43 | print(b1 == b2) 44 | # print(b1 == 42) 45 | 46 | # Check for greater and lesser value 47 | print(b2 >= b1) 48 | print(b2 < b1) 49 | print(b3 >= b2) 50 | 51 | # Now we can sort them 52 | books = [b1, b3, b2, b4] 53 | books.sort() 54 | print([book.title for book in books]) 55 | -------------------------------------------------------------------------------- /Finished/Ch 3/magicstr_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the __str__ and __repr__ magic methods 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, price): 7 | super().__init__() 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | 12 | # The __str__ function is used to return a user-friendly string 13 | # representation of the object 14 | def __str__(self): 15 | return f"{self.title} by {self.author}, costs {self.price}" 16 | 17 | # The __str__ function is used to return a developer-friendly string 18 | # representation of the object 19 | def __repr__(self): 20 | return f"title={self.title},author={self.author},price={self.price}" 21 | 22 | 23 | b1 = Book("War and Peace", "Leo Tolstoy", 39.95) 24 | b2 = Book("The Catcher in the Rye", "JD Salinger", 29.95) 25 | 26 | # print each object 27 | print(b1) 28 | print(b2) 29 | 30 | # use str() and repr() 31 | print(str(b1)) 32 | print(repr(b2)) 33 | -------------------------------------------------------------------------------- /Finished/Ch 4/challenge.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Programming challenge: implement a dataclass 3 | 4 | from dataclasses import dataclass 5 | from abc import ABC, abstractmethod 6 | 7 | @dataclass 8 | class Asset(ABC): 9 | price: float 10 | 11 | @abstractmethod 12 | def __lt__(self, other): 13 | pass 14 | 15 | 16 | @dataclass 17 | class Stock(Asset): 18 | ticker: str 19 | company: str 20 | 21 | def __lt__(self, other): 22 | return self.price < other.price 23 | 24 | @dataclass 25 | class Bond(Asset): 26 | description: str 27 | duration: int 28 | yieldamt: float 29 | 30 | def __lt__(self, other): 31 | return self.yieldamt < other.yieldamt 32 | 33 | 34 | # ~~~~~~~~~ TEST CODE ~~~~~~~~~ 35 | stocks = [ 36 | Stock("MSFT", 342.0, "Microsoft Corp"), 37 | Stock("GOOG", 135.0, "Google Inc"), 38 | Stock("META", 275.0, "Meta Platforms Inc"), 39 | Stock("AMZN", 120.0, "Amazon Inc") 40 | ] 41 | 42 | bonds = [ 43 | Bond(95.31, "30 Year US Treasury", 30, 4.38), 44 | Bond(96.70, "10 Year US Treasury", 10, 4.28), 45 | Bond(98.65, "5 Year US Treasury", 5, 4.43), 46 | Bond(99.57, "2 Year US Treasury", 2, 4.98) 47 | ] 48 | 49 | try: 50 | ast = Asset(100.0) 51 | except: 52 | print("Can't instantiate Asset!") 53 | 54 | stocks.sort() 55 | bonds.sort() 56 | 57 | for stock in stocks: 58 | print(stock) 59 | print("-----------") 60 | for bond in bonds: 61 | print(bond) 62 | -------------------------------------------------------------------------------- /Finished/Ch 4/dataclass_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using data classes to represent data objects 3 | 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass 8 | class Book: 9 | title: str 10 | author: str 11 | pages: int 12 | price: float 13 | 14 | # You can define methods in a dataclass like any other 15 | def bookinfo(self): 16 | return f"{self.title}, by {self.author}" 17 | 18 | 19 | # create some instances 20 | b1 = Book("War and Peace", "Leo Tolstoy", 1225, 39.95) 21 | b2 = Book("The Catcher in the Rye", "JD Salinger", 234, 29.95) 22 | 23 | # access fields 24 | print(b1.title) 25 | print(b2.author) 26 | 27 | # print the book itself - dataclasses provide a default 28 | # implementation of the __repr__ function 29 | print(b1) 30 | 31 | # comparing two dataclasses 32 | b3 = Book("War and Peace", "Leo Tolstoy", 1225, 39.95) 33 | print(b1 == b3) 34 | 35 | # change some fields, call a regular class method 36 | b1.title = "Anna Karenina" 37 | b1.pages = 864 38 | print(b1.bookinfo()) 39 | -------------------------------------------------------------------------------- /Finished/Ch 4/datadefault_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # implementing default values in data classes 3 | 4 | from dataclasses import dataclass, field 5 | import random 6 | 7 | 8 | def price_func(): 9 | return float(random.randrange(20, 40)) 10 | 11 | 12 | @dataclass 13 | class Book: 14 | # you can define default values when attributes are declared 15 | title: str = "No Title" 16 | author: str = "No Author" 17 | pages: int = 0 18 | price: float = field(default_factory=price_func) 19 | 20 | 21 | # Create a default book object 22 | b1 = Book() 23 | print(b1) 24 | 25 | # Create a specified book, price is set by field operator 26 | b1 = Book("War and Peace", "Leo Tolstoy", 1225) 27 | b2 = Book("The Catcher in the Rye", "JD Salinger", 234) 28 | print(b1) 29 | print(b2) 30 | -------------------------------------------------------------------------------- /Finished/Ch 4/immutable_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Creating immutable data classes 3 | 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass(frozen=True) # "The "frozen" parameter makes the class immutable 8 | class ImmutableClass: 9 | value1: str = "Value 1" 10 | value2: int = 0 11 | 12 | def somefunc(self, newval): 13 | self.value2 = newval 14 | 15 | 16 | obj = ImmutableClass() 17 | print(obj) 18 | 19 | # The values can be initialized also, but not subsequently changed 20 | obj2 = ImmutableClass("Another Value", 15) 21 | print(obj2) 22 | 23 | # attempting to change the value of an immutable class throws an exception 24 | obj.value1 = "Another value" 25 | print(obj.value1) 26 | 27 | # Frozen classes can't modify themselves either 28 | obj.somefunc(20) 29 | -------------------------------------------------------------------------------- /Finished/Ch 4/postinit_finished.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the postinit function in data classes 3 | 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass 8 | class Book: 9 | title: str 10 | author: str 11 | pages: int 12 | price: float 13 | 14 | # the __post_init__ function lets us customize additional properties 15 | # after the object has been initialized via built-in __init__ 16 | def __post_init__(self): 17 | self.description = f"{self.title} by {self.author}, {self.pages} pages" 18 | 19 | 20 | # create some Book objects 21 | b1 = Book("War and Peace", "Leo Tolstoy", 1225, 39.95) 22 | b2 = Book("The Catcher in the Rye", "JD Salinger", 234, 29.95) 23 | 24 | # use the description attribute 25 | print(b1.description) 26 | print(b2.description) 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | LinkedIn Learning Exercise Files License Agreement 2 | ================================================== 3 | 4 | This License Agreement (the "Agreement") is a binding legal agreement 5 | between you (as an individual or entity, as applicable) and LinkedIn 6 | Corporation (“LinkedIn”). By downloading or using the LinkedIn Learning 7 | exercise files in this repository (“Licensed Materials”), you agree to 8 | be bound by the terms of this Agreement. If you do not agree to these 9 | terms, do not download or use the Licensed Materials. 10 | 11 | 1. License. 12 | - a. Subject to the terms of this Agreement, LinkedIn hereby grants LinkedIn 13 | members during their LinkedIn Learning subscription a non-exclusive, 14 | non-transferable copyright license, for internal use only, to 1) make a 15 | reasonable number of copies of the Licensed Materials, and 2) make 16 | derivative works of the Licensed Materials for the sole purpose of 17 | practicing skills taught in LinkedIn Learning courses. 18 | - b. Distribution. Unless otherwise noted in the Licensed Materials, subject 19 | to the terms of this Agreement, LinkedIn hereby grants LinkedIn members 20 | with a LinkedIn Learning subscription a non-exclusive, non-transferable 21 | copyright license to distribute the Licensed Materials, except the 22 | Licensed Materials may not be included in any product or service (or 23 | otherwise used) to instruct or educate others. 24 | 25 | 2. Restrictions and Intellectual Property. 26 | - a. You may not to use, modify, copy, make derivative works of, publish, 27 | distribute, rent, lease, sell, sublicense, assign or otherwise transfer the 28 | Licensed Materials, except as expressly set forth above in Section 1. 29 | - b. Linkedin (and its licensors) retains its intellectual property rights 30 | in the Licensed Materials. Except as expressly set forth in Section 1, 31 | LinkedIn grants no licenses. 32 | - c. You indemnify LinkedIn and its licensors and affiliates for i) any 33 | alleged infringement or misappropriation of any intellectual property rights 34 | of any third party based on modifications you make to the Licensed Materials, 35 | ii) any claims arising from your use or distribution of all or part of the 36 | Licensed Materials and iii) a breach of this Agreement. You will defend, hold 37 | harmless, and indemnify LinkedIn and its affiliates (and our and their 38 | respective employees, shareholders, and directors) from any claim or action 39 | brought by a third party, including all damages, liabilities, costs and 40 | expenses, including reasonable attorneys’ fees, to the extent resulting from, 41 | alleged to have resulted from, or in connection with: (a) your breach of your 42 | obligations herein; or (b) your use or distribution of any Licensed Materials. 43 | 44 | 3. Open source. This code may include open source software, which may be 45 | subject to other license terms as provided in the files. 46 | 47 | 4. Warranty Disclaimer. LINKEDIN PROVIDES THE LICENSED MATERIALS ON AN “AS IS” 48 | AND “AS AVAILABLE” BASIS. LINKEDIN MAKES NO REPRESENTATION OR WARRANTY, 49 | WHETHER EXPRESS OR IMPLIED, ABOUT THE LICENSED MATERIALS, INCLUDING ANY 50 | REPRESENTATION THAT THE LICENSED MATERIALS WILL BE FREE OF ERRORS, BUGS OR 51 | INTERRUPTIONS, OR THAT THE LICENSED MATERIALS ARE ACCURATE, COMPLETE OR 52 | OTHERWISE VALID. TO THE FULLEST EXTENT PERMITTED BY LAW, LINKEDIN AND ITS 53 | AFFILIATES DISCLAIM ANY IMPLIED OR STATUTORY WARRANTY OR CONDITION, INCLUDING 54 | ANY IMPLIED WARRANTY OR CONDITION OF MERCHANTABILITY OR FITNESS FOR A 55 | PARTICULAR PURPOSE, AVAILABILITY, SECURITY, TITLE AND/OR NON-INFRINGEMENT. 56 | YOUR USE OF THE LICENSED MATERIALS IS AT YOUR OWN DISCRETION AND RISK, AND 57 | YOU WILL BE SOLELY RESPONSIBLE FOR ANY DAMAGE THAT RESULTS FROM USE OF THE 58 | LICENSED MATERIALS TO YOUR COMPUTER SYSTEM OR LOSS OF DATA. NO ADVICE OR 59 | INFORMATION, WHETHER ORAL OR WRITTEN, OBTAINED BY YOU FROM US OR THROUGH OR 60 | FROM THE LICENSED MATERIALS WILL CREATE ANY WARRANTY OR CONDITION NOT 61 | EXPRESSLY STATED IN THESE TERMS. 62 | 63 | 5. Limitation of Liability. LINKEDIN SHALL NOT BE LIABLE FOR ANY INDIRECT, 64 | INCIDENTAL, SPECIAL, PUNITIVE, CONSEQUENTIAL OR EXEMPLARY DAMAGES, INCLUDING 65 | BUT NOT LIMITED TO, DAMAGES FOR LOSS OF PROFITS, GOODWILL, USE, DATA OR OTHER 66 | INTANGIBLE LOSSES . IN NO EVENT WILL LINKEDIN'S AGGREGATE LIABILITY TO YOU 67 | EXCEED $100. THIS LIMITATION OF LIABILITY SHALL: 68 | - i. APPLY REGARDLESS OF WHETHER (A) YOU BASE YOUR CLAIM ON CONTRACT, TORT, 69 | STATUTE, OR ANY OTHER LEGAL THEORY, (B) WE KNEW OR SHOULD HAVE KNOWN ABOUT 70 | THE POSSIBILITY OF SUCH DAMAGES, OR (C) THE LIMITED REMEDIES PROVIDED IN THIS 71 | SECTION FAIL OF THEIR ESSENTIAL PURPOSE; AND 72 | - ii. NOT APPLY TO ANY DAMAGE THAT LINKEDIN MAY CAUSE YOU INTENTIONALLY OR 73 | KNOWINGLY IN VIOLATION OF THESE TERMS OR APPLICABLE LAW, OR AS OTHERWISE 74 | MANDATED BY APPLICABLE LAW THAT CANNOT BE DISCLAIMED IN THESE TERMS. 75 | 76 | 6. Termination. This Agreement automatically terminates upon your breach of 77 | this Agreement or termination of your LinkedIn Learning subscription. On 78 | termination, all licenses granted under this Agreement will terminate 79 | immediately and you will delete the Licensed Materials. Sections 2-7 of this 80 | Agreement survive any termination of this Agreement. LinkedIn may discontinue 81 | the availability of some or all of the Licensed Materials at any time for any 82 | reason. 83 | 84 | 7. Miscellaneous. This Agreement will be governed by and construed in 85 | accordance with the laws of the State of California without regard to conflict 86 | of laws principles. The exclusive forum for any disputes arising out of or 87 | relating to this Agreement shall be an appropriate federal or state court 88 | sitting in the County of Santa Clara, State of California. If LinkedIn does 89 | not act to enforce a breach of this Agreement, that does not mean that 90 | LinkedIn has waived its right to enforce this Agreement. The Agreement does 91 | not create a partnership, agency relationship, or joint venture between the 92 | parties. Neither party has the power or authority to bind the other or to 93 | create any obligation or responsibility on behalf of the other. You may not, 94 | without LinkedIn’s prior written consent, assign or delegate any rights or 95 | obligations under these terms, including in connection with a change of 96 | control. Any purported assignment and delegation shall be ineffective. The 97 | Agreement shall bind and inure to the benefit of the parties, their respective 98 | successors and permitted assigns. If any provision of the Agreement is 99 | unenforceable, that provision will be modified to render it enforceable to the 100 | extent possible to give effect to the parties’ intentions and the remaining 101 | provisions will not be affected. This Agreement is the only agreement between 102 | you and LinkedIn regarding the Licensed Materials, and supersedes all prior 103 | agreements relating to the Licensed Materials. 104 | 105 | Last Updated: March 2019 106 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Copyright 2023 LinkedIn Corporation 2 | All Rights Reserved. 3 | 4 | Licensed under the LinkedIn Learning Exercise File License (the "License"). 5 | See LICENSE in the project root for license information. 6 | 7 | Please note, this project may automatically load third party code from external 8 | repositories (for example, NPM modules, Composer packages, or other dependencies). 9 | If so, such third party code may be subject to other license terms than as set 10 | forth above. In addition, such third party code may also depend on and load 11 | multiple tiers of dependencies. Please review the applicable licenses of the 12 | additional dependencies. 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python Object-Oriented Programming 2 | This is the repository for the LinkedIn Learning course Python Object-Oriented Programming. The full course is available from [LinkedIn Learning][lil-course-url]. 3 | 4 | ![Python Object-Oriented Programming][lil-thumbnail-url] 5 | 6 | The object-oriented programming (OOP) features in Python make it easier to build programs of increasing complexity and modularity. In this course with instructor Joe Marini, learn how to apply core OOP principles to build programs that are extensible and efficient. Joe starts with the basics of defining and using classes and objects. Then he moves into more advanced features like abstract base classes and how to implement interfaces. He also details some of the more unique features of Python, like magic class methods to make your classes integrate tightly with the Python language and data classes to dramatically reduce the amount of boilerplate code needed to build data-centric objects. 7 | 8 | ## Instructions 9 | This repository contains two folders for the contents of the course: 10 | - *Finished*: The fully finished versions of the code examples. Intended to be used as a reference and for help with troubleshooting your own code 11 | - *Start*: The starting point for each exercise. This is the code that you will use in the course to build towards the finished examples. 12 | 13 | ## Installing 14 | 1. To use these exercise files locally on your computer, you must have the following installed: 15 | - [Python][python-download] 16 | 2. Clone this repository into your local machine using the terminal (Mac), CMD (Windows), or a GUI tool like SourceTree. 17 | 18 | ## Codespace 19 | This course has been set up to use Codespaces, an online development environment that requires no installation. Fork a copy of the repository in your own Github account and use a Codespace to work entirely online. 20 | 21 | 22 | ### Instructor 23 | 24 | Joe Marini 25 | 26 | Senior Director of Product and Engineering 27 | 28 | 29 | 30 | Check out my other courses on [LinkedIn Learning](https://www.linkedin.com/learning/instructors/joe-marini). 31 | 32 | [lil-course-url]: https://www.linkedin.com/learning/python-object-oriented-programming-22888296?dApp=59033956&leis=LAA 33 | [lil-thumbnail-url]: https://media.licdn.com/dms/image/D4E0DAQGmlDAUUSloow/learning-public-crop_675_1200/0/1697645724849?e=2147483647&v=beta&t=Ws35uIg4NrNGWXqHuaX4LoGzK4DvrQjZu5Q6QJQ_SqM 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /Start/Ch 1/challenge.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Programming challenge: define a class to represent a stock symbol 3 | 4 | # Challenge: create a class to represent stock information. 5 | # Your class should have properties for: 6 | # Ticker (string) 7 | # Price (float) 8 | # Company (string) 9 | # And a method get_description() which returns a string in the form 10 | # of "Ticker: Company -- $Price" 11 | 12 | class Stock: 13 | pass 14 | 15 | # ~~~~~~~~~ TEST CODE ~~~~~~~~~ 16 | msft = Stock("MSFT", 342.0, "Microsoft Corp") 17 | goog = Stock("GOOG", 135.0, "Google Inc") 18 | meta = Stock("META", 275.0, "Meta Platforms Inc") 19 | amzn = Stock("AMZN", 135.0, "Amazon Inc") 20 | 21 | print(msft.get_description()) 22 | print(goog.get_description()) 23 | print(meta.get_description()) 24 | print(amzn.get_description()) 25 | -------------------------------------------------------------------------------- /Start/Ch 1/class_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using class-level and static methods 3 | 4 | 5 | class Book: 6 | # TODO: Properties defined at the class level are shared by all instances 7 | 8 | # TODO: double-underscore properties are hidden from other classes 9 | 10 | # TODO: create a class method 11 | 12 | # TODO: create a static method 13 | 14 | # instance methods receive a specific object instance as an argument 15 | # and operate on data specific to that object instance 16 | def set_title(self, newtitle): 17 | self.title = newtitle 18 | 19 | def __init__(self, title): 20 | self.title = title 21 | 22 | 23 | # TODO: access the class attribute 24 | 25 | 26 | # TODO: Create some book instances 27 | 28 | 29 | # TODO: Use the static method to access a singleton object 30 | -------------------------------------------------------------------------------- /Start/Ch 1/definition_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Basic class definitions 3 | 4 | 5 | # TODO: create a basic class 6 | 7 | 8 | # TODO: create instances of the class 9 | 10 | 11 | # TODO: print the class and property 12 | -------------------------------------------------------------------------------- /Start/Ch 1/instance_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using instance methods and attributes 3 | 4 | 5 | class Book: 6 | # the "init" function is called when the instance is 7 | # created and ready to be initialized 8 | def __init__(self, title): 9 | self.title = title 10 | # TODO: add properties 11 | 12 | # TODO: create instance methods 13 | 14 | 15 | # TODO: create some book instances 16 | b1 = Book("War and Peace") 17 | b2 = Book("The Catcher in the Rye") 18 | 19 | # TODO: print the price of book1 20 | 21 | 22 | # TODO: try setting the discount 23 | 24 | 25 | # TODO: properties with double underscores are hidden by the interpreter 26 | -------------------------------------------------------------------------------- /Start/Ch 1/typecheck_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Checking class types and instances 3 | 4 | 5 | class Book: 6 | def __init__(self, title): 7 | self.title = title 8 | 9 | 10 | class Newspaper: 11 | def __init__(self, name): 12 | self.name = name 13 | 14 | 15 | # Create some instances of the classes 16 | b1 = Book("The Catcher In The Rye") 17 | b2 = Book("The Grapes of Wrath") 18 | n1 = Newspaper("The Washington Post") 19 | n2 = Newspaper("The New York Times") 20 | 21 | # TODO: use type() to inspect the object type 22 | 23 | 24 | # TODO: compare two types together 25 | 26 | 27 | # TODO: use isinstance to compare a specific instance to a known type 28 | -------------------------------------------------------------------------------- /Start/Ch 2/abstract_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using Abstract Base Classes to enforce class constraints 3 | 4 | 5 | class GraphicShape: 6 | def __init__(self): 7 | super().__init__() 8 | 9 | def calcArea(self): 10 | pass 11 | 12 | 13 | class Circle(GraphicShape): 14 | def __init__(self, radius): 15 | self.radius = radius 16 | 17 | 18 | class Square(GraphicShape): 19 | def __init__(self, side): 20 | self.side = side 21 | 22 | 23 | g = GraphicShape() 24 | 25 | c = Circle(10) 26 | print(c.calcArea()) 27 | s = Square(12) 28 | print(s.calcArea()) 29 | -------------------------------------------------------------------------------- /Start/Ch 2/challenge.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Programming challenge: use inheritance and abstract classes 3 | 4 | # Challenge: create a class structure to represent stocks and bonds 5 | # Requirements: 6 | # -- Both stocks and bonds have a price 7 | # -- Stocks have a company name and ticker 8 | # -- Bonds have a description, duration, and yield 9 | # -- You should not be able to instantiate the base class 10 | # -- Subclasses are required to override get_description() 11 | # -- get_description returns formats for stocks and bonds 12 | # For stocks: "Ticker: Company -- $Price" 13 | # For bonds: "description: duration'yr' : $price : yieldamt%" 14 | 15 | class Asset(): 16 | pass 17 | 18 | class Stock(): 19 | pass 20 | 21 | class Bond(): 22 | pass 23 | 24 | 25 | # ~~~~~~~~~ TEST CODE ~~~~~~~~~ 26 | try: 27 | ast = Asset(100.0) 28 | except: 29 | print("Can't instantiate Asset!") 30 | 31 | msft = Stock("MSFT", 342.0, "Microsoft Corp") 32 | goog = Stock("GOOG", 135.0, "Google Inc") 33 | meta = Stock("META", 275.0, "Meta Platforms Inc") 34 | amzn = Stock("AMZN", 135.0, "Amazon Inc") 35 | 36 | us30yr = Bond(95.31, "30 Year US Treasury", 30, 4.38) 37 | us10yr = Bond(96.70, "10 Year US Treasury", 10, 4.28) 38 | us5yr = Bond(98.65, "5 Year US Treasury", 5, 4.43) 39 | us2yr = Bond(99.57, "2 Year US Treasury", 2, 4.98) 40 | 41 | print(msft.get_description()) 42 | print(goog.get_description()) 43 | print(meta.get_description()) 44 | print(amzn.get_description()) 45 | 46 | print(us30yr.get_description()) 47 | print(us10yr.get_description()) 48 | print(us5yr.get_description()) 49 | print(us2yr.get_description()) 50 | -------------------------------------------------------------------------------- /Start/Ch 2/composition_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using composition to build complex objects 3 | 4 | 5 | class Book: 6 | def __init__(self, title, price, authorfname, authorlname): 7 | self.title = title 8 | self.price = price 9 | 10 | self.authorfname = authorfname 11 | self.authorlname = authorlname 12 | 13 | self.chapters = [] 14 | 15 | def addchapter(self, name, pages): 16 | self.chapters.append((name, pages)) 17 | 18 | 19 | b1 = Book("War and Peace", 39.0, "Leo", "Tolstoy") 20 | 21 | b1.addchapter("Chapter 1", 125) 22 | b1.addchapter("Chapter 2", 97) 23 | b1.addchapter("Chapter 3", 143) 24 | 25 | print(b1.title) 26 | -------------------------------------------------------------------------------- /Start/Ch 2/inheritance_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Understanding class inheritance 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, pages, price): 7 | self.title = title 8 | self.price = price 9 | self.author = author 10 | self.pages = pages 11 | 12 | 13 | class Magazine: 14 | def __init__(self, title, publisher, price, period): 15 | self.title = title 16 | self.price = price 17 | self.period = period 18 | self.publisher = publisher 19 | 20 | 21 | class Newspaper: 22 | def __init__(self, title, publisher, price, period): 23 | self.title = title 24 | self.price = price 25 | self.period = period 26 | self.publisher = publisher 27 | 28 | 29 | b1 = Book("Brave New World", "Aldous Huxley", 311, 29.0) 30 | n1 = Newspaper("NY Times", "New York Times Company", 6.0, "Daily") 31 | m1 = Magazine("Scientific American", "Springer Nature", 5.99, "Monthly") 32 | 33 | print(b1.author) 34 | print(n1.publisher) 35 | print(b1.price, m1.price, n1.price) 36 | -------------------------------------------------------------------------------- /Start/Ch 2/interface_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using Abstract Base Classes to implement interfaces 3 | 4 | from abc import ABC, abstractmethod 5 | 6 | 7 | class GraphicShape(ABC): 8 | def __init__(self): 9 | super().__init__() 10 | 11 | @abstractmethod 12 | def calcArea(self): 13 | pass 14 | 15 | 16 | class Circle(GraphicShape): 17 | def __init__(self, radius): 18 | self.radius = radius 19 | 20 | def calcArea(self): 21 | return 3.14 * (self.radius ** 2) 22 | 23 | 24 | c = Circle(10) 25 | print(c.calcArea()) 26 | -------------------------------------------------------------------------------- /Start/Ch 2/multiple_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Understanding multiple inheritance 3 | 4 | 5 | class A: 6 | def __init__(self): 7 | super().__init__() 8 | self.prop1 = "prop1" 9 | 10 | 11 | class B: 12 | def __init__(self): 13 | super().__init__() 14 | self.prop2 = "prop2" 15 | 16 | 17 | class C(A, B): 18 | def __init__(self): 19 | super().__init__() 20 | 21 | 22 | c = C() 23 | -------------------------------------------------------------------------------- /Start/Ch 3/challenge.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Programming challenge: add methods for comparison and equality 3 | 4 | # Challenge: use a magic method to make stocks and bonds sortable 5 | # Stocks should sort from low to high on price 6 | # Bonds should sort from low to high on yield 7 | 8 | from abc import ABC, abstractmethod 9 | 10 | 11 | class Asset(ABC): 12 | def __init__(self, price): 13 | self.price = price 14 | 15 | @abstractmethod 16 | def __str__(self): 17 | pass 18 | 19 | 20 | class Stock(Asset): 21 | def __init__(self, ticker, price, company): 22 | super().__init__(price) 23 | self.company = company 24 | self.ticker = ticker 25 | 26 | 27 | class Bond(Asset): 28 | def __init__(self, price, description, duration, yieldamt): 29 | super().__init__(price) 30 | self.description = description 31 | self.duration = duration 32 | self.yieldamt = yieldamt 33 | 34 | 35 | # ~~~~~~~~~ TEST CODE ~~~~~~~~~ 36 | stocks = [ 37 | Stock("MSFT", 342.0, "Microsoft Corp"), 38 | Stock("GOOG", 135.0, "Google Inc"), 39 | Stock("META", 275.0, "Meta Platforms Inc"), 40 | Stock("AMZN", 120.0, "Amazon Inc") 41 | ] 42 | 43 | bonds = [ 44 | Bond(95.31, "30 Year US Treasury", 30, 4.38), 45 | Bond(96.70, "10 Year US Treasury", 10, 4.28), 46 | Bond(98.65, "5 Year US Treasury", 5, 4.43), 47 | Bond(99.57, "2 Year US Treasury", 2, 4.98) 48 | ] 49 | 50 | stocks.sort() 51 | bonds.sort() 52 | 53 | for stock in stocks: 54 | print(stock) 55 | print("-----------") 56 | for bond in bonds: 57 | print(bond) 58 | -------------------------------------------------------------------------------- /Start/Ch 3/magicattr_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the __str__ and __repr__ magic methods 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, price): 7 | super().__init__() 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | self._discount = 0.1 12 | 13 | # The __str__ function is used to return a user-friendly string 14 | # representation of the object 15 | def __str__(self): 16 | return f"{self.title} by {self.author}, costs {self.price}" 17 | 18 | # TODO: __getattribute__ called when an attr is retrieved. Don't 19 | # directly access the attr name otherwise a recursive loop is created 20 | 21 | # TODO: __setattr__ called when an attribute value is set. Don't set the attr 22 | # directly here otherwise a recursive loop causes a crash 23 | 24 | # TODO: __getattr__ called when __getattribute__ lookup fails - you can 25 | # pretty much generate attributes on the fly with this method 26 | 27 | 28 | b1 = Book("War and Peace", "Leo Tolstoy", 39.95) 29 | b2 = Book("The Catcher in the Rye", "JD Salinger", 29.95) 30 | -------------------------------------------------------------------------------- /Start/Ch 3/magiccall_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the __str__ and __repr__ magic methods 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, price): 7 | super().__init__() 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | 12 | def __str__(self): 13 | return f"{self.title} by {self.author}, costs {self.price}" 14 | 15 | # TODO: the __call__ method can be used to call the object like a function 16 | 17 | 18 | b1 = Book("War and Peace", "Leo Tolstoy", 39.95) 19 | b2 = Book("The Catcher in the Rye", "JD Salinger", 29.95) 20 | 21 | # TODO: call the object as if it were a function 22 | -------------------------------------------------------------------------------- /Start/Ch 3/magiceq_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the __str__ and __repr__ magic methods 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, price): 7 | super().__init__() 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | 12 | # TODO: the __eq__ method checks for equality between two objects 13 | 14 | # TODO: the __ge__ establishes >= relationship with another obj 15 | 16 | # TODO: the __lt__ establishes < relationship with another obj 17 | 18 | 19 | b1 = Book("War and Peace", "Leo Tolstoy", 39.95) 20 | b2 = Book("The Catcher in the Rye", "JD Salinger", 29.95) 21 | b3 = Book("War and Peace", "Leo Tolstoy", 39.95) 22 | b4 = Book("To Kill a Mockingbird", "Harper Lee", 24.95) 23 | 24 | # TODO: Check for equality 25 | 26 | 27 | # TODO: Check for greater and lesser value 28 | 29 | 30 | # TODO: Now we can sort them too 31 | -------------------------------------------------------------------------------- /Start/Ch 3/magicstr_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the __str__ and __repr__ magic methods 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, price): 7 | super().__init__() 8 | self.title = title 9 | self.author = author 10 | self.price = price 11 | 12 | # TODO: use the __str__ method to return a string 13 | 14 | # TODO: use the __repr__ method to return an obj representation 15 | 16 | 17 | b1 = Book("War and Peace", "Leo Tolstoy", 39.95) 18 | b2 = Book("The Catcher in the Rye", "JD Salinger", 29.95) 19 | 20 | print(b1) 21 | print(b2) 22 | -------------------------------------------------------------------------------- /Start/Ch 4/challenge.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Programming challenge: implement a dataclass 3 | 4 | # Challenge: convert your classes to dataclasses 5 | # The subclasses are required to override the magic method 6 | # that makes them sortable 7 | 8 | class Asset(): 9 | pass 10 | 11 | 12 | class Stock(Asset): 13 | pass 14 | 15 | 16 | class Bond(Asset): 17 | pass 18 | 19 | # ~~~~~~~~~ TEST CODE ~~~~~~~~~ 20 | stocks = [ 21 | Stock("MSFT", 342.0, "Microsoft Corp"), 22 | Stock("GOOG", 135.0, "Google Inc"), 23 | Stock("META", 275.0, "Meta Platforms Inc"), 24 | Stock("AMZN", 120.0, "Amazon Inc") 25 | ] 26 | 27 | bonds = [ 28 | Bond(95.31, "30 Year US Treasury", 30, 4.38), 29 | Bond(96.70, "10 Year US Treasury", 10, 4.28), 30 | Bond(98.65, "5 Year US Treasury", 5, 4.43), 31 | Bond(99.57, "2 Year US Treasury", 2, 4.98) 32 | ] 33 | 34 | try: 35 | ast = Asset(100.0) 36 | except: 37 | print("Can't instantiate Asset!") 38 | 39 | stocks.sort() 40 | bonds.sort() 41 | 42 | for stock in stocks: 43 | print(stock) 44 | print("-----------") 45 | for bond in bonds: 46 | print(bond) 47 | -------------------------------------------------------------------------------- /Start/Ch 4/dataclass_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using data classes to represent data objects 3 | 4 | 5 | class Book: 6 | def __init__(self, title, author, pages, price): 7 | self.title = title 8 | self.author = author 9 | self.pages = pages 10 | self.price = price 11 | 12 | 13 | # create some instances 14 | b1 = Book("War and Peace", "Leo Tolstoy", 1225, 39.95) 15 | b2 = Book("The Catcher in the Rye", "JD Salinger", 234, 29.95) 16 | 17 | # access fields 18 | print(b1.title) 19 | print(b2.author) 20 | 21 | # TODO: print the book itself - dataclasses implement __repr__ 22 | 23 | 24 | # TODO: comparing two dataclasses - they implement __eq__ 25 | 26 | 27 | # TODO: change some fields 28 | -------------------------------------------------------------------------------- /Start/Ch 4/datadefault_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # implementing default values in data classes 3 | 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass 8 | class Book: 9 | # you can define default values when attributes are declared 10 | title: str 11 | author: str 12 | pages: int 13 | price: float 14 | -------------------------------------------------------------------------------- /Start/Ch 4/immutable_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Creating immutable data classes 3 | 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass() # TODO: "The "frozen" parameter makes the class immutable 8 | class ImmutableClass: 9 | value1: str = "Value 1" 10 | value2: int = 0 11 | 12 | 13 | obj = ImmutableClass() 14 | print(obj.value1) 15 | 16 | # TODO: attempting to change the value of an immutable class throws an exception 17 | 18 | 19 | # TODO: even functions within the class can't change anything 20 | -------------------------------------------------------------------------------- /Start/Ch 4/postinit_start.py: -------------------------------------------------------------------------------- 1 | # Python Object Oriented Programming by Joe Marini course example 2 | # Using the postinit function in data classes 3 | 4 | from dataclasses import dataclass 5 | 6 | 7 | @dataclass 8 | class Book: 9 | title: str 10 | author: str 11 | pages: int 12 | price: float 13 | 14 | # TODO: the __post_init__ function lets us customize additional properties 15 | # after the object has been initialized via built-in __init__ 16 | 17 | 18 | # create some Book objects 19 | b1 = Book("War and Peace", "Leo Tolstoy", 1225, 39.95) 20 | b2 = Book("The Catcher in the Rye", "JD Salinger", 234, 29.95) 21 | 22 | # TODO: use the description attribute 23 | --------------------------------------------------------------------------------