[{"content":"What is AI? Some tasks are easy for computers to perform, such as:\nIdentifying the maximum number in a dataset Calculating the shortest route from your office to the subway station For these types of problems, we can provide clear, step-by-step instructions that computers execute reliably. However, other tasks are much harder to solve with traditional programming:\nIs this person happy? Is that a cat in this image? While these questions might seem straightforward for humans to answer, they\u0026rsquo;re surprisingly difficult to program explicitly. Artificial Intelligence (AI) refers to systems that can perform tasks typically requiring human intelligence — such as recognizing patterns, understanding language, and making decisions based on complex data.\nRather than following rigid instructions, AI systems learn from examples and experience, allowing them to handle tasks that are difficult or impossible to solve with conventional programming alone.\nWhat is Machine Learning? Machine learning is a subset of AI where computers learn patterns from data rather than following explicit instructions. A machine learning system analyzes examples, identifies patterns, and uses what it has learned to make predictions or decisions about new, similar data.\nThe key difference from traditional programming:\nTraditional Programming: You provide explicit rules → the computer follows them → produces output\nAnalogy: Following a recipe step-by-step to cook a meal Machine Learning: You provide examples (data) → the computer finds patterns → creates a model that can handle new situations\nAnalogy: Tasting a dish at a restaurant and figuring out the ingredients and techniques (recipe) to recreate it at home For instance, instead of writing rules like \u0026ldquo;if the email contains the word \u0026lsquo;prize\u0026rsquo; and asks for personal information, it\u0026rsquo;s spam,\u0026rdquo; a machine learning system learns what spam looks like by analyzing thousands of examples of spam and legitimate emails, then identifies patterns we might never have thought to program explicitly.\nAI, Machine Learning \u0026amp; Deep Learning You\u0026rsquo;ve probably heard these terms used interchangeably, but they\u0026rsquo;re actually nested concepts:\nArtificial Intelligence (AI) is the broadest concept — any technique that enables computers to mimic human intelligence Machine Learning (ML) is a subset of AI—systems that learn from data without being explicitly programmed Deep Learning (DL) is a subset of Machine Learning — uses neural networks with multiple layers to learn complex patterns Example to illustrate the difference:\nAI: A chess-playing computer (could use rule-based logic or learning) Machine Learning: A spam filter that improves by analyzing thousands of emails Deep Learning: Image recognition that identifies cats in photos by learning from millions of images through neural networks AI vs. Data Science While AI and Data Science both work with data and often use similar tools, they have different primary goals:\nAspect AI Data Science Primary Goal Enable machines to perform intelligent tasks (perception, reasoning, decision-making) Extract insights and knowledge from data to inform decisions Focus Automating intelligent behavior Understanding patterns and answering questions Key Techniques Machine learning, neural networks, computer vision, natural language processing Statistics, data analysis, machine learning, visualization Typical Output A system that performs tasks (chatbot, recommendation engine, autonomous vehicle) Insights, reports, predictions, dashboards Example Applications Virtual assistants (Siri, Alexa), self-driving cars, facial recognition Customer churn analysis, sales forecasting, A/B testing, market research The overlap: Data scientists often use AI/ML techniques, and AI systems require data science skills to prepare and analyze data. Many professionals work at the intersection of both fields.\nCommon AI Terms Explained As AI evolves rapidly, new terminology emerges constantly. Here are some current buzzwords simplified:\nLLM (Large Language Model) - AI systems trained on massive amounts of text that can understand and generate human - like language. Examples: ChatGPT, Claude, GPT-4. Think of them as extremely well-read assistants that can write, answer questions, and help with various language tasks.\nAgent - An AI system that can take actions autonomously to achieve goals, not just respond to questions. Unlike a basic chatbot that only answers when asked, an agent can plan multiple steps, use tools, and complete complex tasks with minimal human supervision. Example: an AI that researches a topic, writes a report, and emails it to you.\nMCP (Model Context Protocol) - A standard way for AI systems to connect with external tools and data sources. It\u0026rsquo;s like giving your AI assistant the ability to check your calendar, access your files, or pull data from various apps—all through a unified interface.\nSkill - A specialized capability or function that an AI system can perform, often by connecting to specific tools or following particular workflows. Think of skills as apps for your AI—each one teaches it how to do something specific like analyzing spreadsheets, generating images, or searching databases.\nRAG (Retrieval-Augmented Generation) - A technique where AI retrieves relevant information from a knowledge base before generating a response, making answers more accurate and up-to-date. It\u0026rsquo;s like giving the AI a library to reference instead of relying only on what it memorized during training.\nFine-tuning - Customizing a pre-trained AI model for specific tasks or domains by training it further on specialized data. Like teaching a general doctor to become a heart surgeon — the foundation is there, but you add expertise in a particular area.\nMCP vs. Skill: What\u0026rsquo;s the Difference? These terms are related but serve different purposes:\nAspect MCP (Model Context Protocol) Skill What it is The connection standard or \u0026ldquo;language\u0026rdquo; that lets AI communicate with external systems The actual capability or know-how that AI uses to accomplish specific tasks Analogy The USB port on your computer The USB device you plug in (printer, camera, keyboard) Technical role Infrastructure/protocol layer Application/capability layer Example The protocol that allows Claude to connect to Google Drive The \u0026ldquo;document editing skill\u0026rdquo; that knows how to read, analyze, and modify Word files Focus How AI connects to tools What the AI can do with those tools In practice: An AI system might use MCP to connect to various services (email, calendar, databases), and then use different skills to actually work with the data from those services — like a \u0026ldquo;meeting scheduler\u0026rdquo; skill or \u0026ldquo;data analysis\u0026rdquo; skill. MCP is the bridge; skills are what you accomplish after crossing it.\n","date":"2026-01-18T21:35:08+08:00","permalink":"https://blog.cxu.my.id/post/artifical-intelligence/101/","title":"AI 101"},{"content":"What is Machine Learning? Refer to this link\nML is NOT the solution to every problem. If simple rules work, use them instead.\nMain types of ML\nSupervised learning - you have data and labels. The algorithm learns by predicting labels and correcting mistakes.\nClassification - categorizing samples (binary for 2 options, multi-class for 3+) Regression - predicting numbers (e.g., \u0026ldquo;how many users will subscribe?\u0026rdquo;) Unsupervised learning - you have data but no labels. The algorithm finds patterns, and you interpret them. Example: clustering customers into \u0026ldquo;summer buyers\u0026rdquo; and \u0026ldquo;winter buyers\u0026rdquo; for targeted promotions.\nTransfer learning - reusing a trained model for a new task (e.g., adapting a car recognition model to identify dog breeds). Valuable because training models from scratch is expensive.\nReinforcement learning - the algorithm learns by trial and error within defined rules, earning rewards or penalties. Example: teaching an AI to play chess by updating its score based on moves.\nMatching your problem Supervised learning - you know inputs and outputs Unsupervised learning - you have inputs but uncertain outputs Transfer learning - your problem resembles an existing one Typical Machine Learning Flow Problem → Data → Evaluation → Features → Modeling → Experiments Problem - What are we trying to solve? Data - What data do we have? Evaluation - What does success look like? Features - Which variables should we feed into the model? Modeling - Which model fits our problem best? Experiments - What else can we try to improve results? Evaluation Evaluation defines what success looks like. Common metrics include:\nAccuracy - How often is the model correct overall?\n$Accuracy = \\frac {TP + TN} {TP + TN + FP + FN}$\nPrecision - When the model predicts positive, how often is it right?\n$Precision = \\frac {TP} {TP + FP}$\nRecall - Of all actual positives, how many did the model catch?\n$Recall = \\frac {TP} {TP + FN}$\nWhen to prioritize each:\nHigh precision - Use when false positives are costly. Example: spam filters (don\u0026rsquo;t want important emails marked as spam). The model acts more deliberately, so when it reports positive, it is very likely to be actual positive. High recall - Use when false negatives are costly. Example: cancer detection (missing a case is worse than a false alarm). The modle becomes more sensitive. It may reports False Positive, but it tries its best to catch all \u0026lsquo;potential\u0026rsquo; positives. Modeling Modeling is the core of the ML workflow, consisting of three stages:\nTraining - train the model on data Validation - tune the model\u0026rsquo;s parameters Test - verify the model\u0026rsquo;s performance The dataset is split accordingly:\nSet Split Training 70% – 80% Validation 10% – 15% Test 10% – 15% A key goal is generalization — a good model performs well on data it has never seen before, producing similar results across all three sets.\nWatch out for these two failure modes:\nData Set Underfitting Overfitting Training 62% 95% Test 50% 60% Underfitting - poor accuracy across the board; the model is too simple for the problem. Overfitting - high training accuracy but poor test accuracy; the model memorized the training data instead of learning the underlying pattern. Commonly Used Tools Core Python Libraries:\nNumPy - numerical computing with arrays and matrices Pandas - data manipulation and analysis Matplotlib/Seaborn - data visualization ML Frameworks:\nScikit-learn - classical ML algorithms (regression, classification, clustering) TensorFlow - Google\u0026rsquo;s deep learning framework PyTorch - Facebook\u0026rsquo;s deep learning framework, popular in research Keras - high-level neural network API (now multi-backend with TensorFlow, PyTorch and JAX) Development Tools:\nJupyter Notebook - interactive coding environment Google Colab - free cloud-based Jupyter notebooks with GPU access Anaconda/Miniconda - Python distribution with package management Optional but Popular:\nMLflow - experiment tracking and model management Hugging Face - pre-trained models for NLP tasks ","date":"2026-01-18T21:35:08+08:00","permalink":"https://blog.cxu.my.id/post/artifical-intelligence/machine-learning/101/","title":"Machine Learning 101"},{"content":"Fundamental Primitive Types Primitive types in Scala are like the ones in Java, but they are capitalized\nInt: 32-bit integers Long: 32-bit integers Float: 32-bit floating point numbers Double: 64-bit floating point numbers Char: 16-bit unicode characters Short: 16-bit integers Byte: 8-bit integers Boolean: boolean values true and false scala\u0026gt; val x = 23 val x: Int = 23 Expression Scala supports functional programming quite well. Many items are expressions in Scala, though in many other languages they are not treated as expressions. For example the if...then...else expression\nval x = if true then 10 else 20 A block is denoted by a pair of curly braces {}. A block is an expression, and it uses the last expression as its value.\nval x = { val y = 10 y + 5 } // the block is evaluated to 15, so x is 15 In Scala 3, braces are optional, so below code snippet is valid.\nscala\u0026gt; val x2 = | val y2 = 20 | y2 * 3 | val x2: Int = 60 To declare a variable, use var. However, following functional programming style, mutation is encouraged, and use val in immutation case.\nval x = 1 val y: Int = 2 var sum = 0 Evaluation To evaluate an expression\nTake the leftmost operator. Evaluate its operands, left to right for multiple operands case. Apply the operator to the operands. There are two evaluation strategies regarding function invocation\nApplicative order(call by value): reduce function arguments to values before rewriting function application by default used in Scala the expression evaluates at most once Normal order(call by name): apply function directly without reducing arguments first use =\u0026gt; to indicate normal order before parameters in function signature no need to do unnecessary evaluation The two evaluation strategies reduce to the same final result as long as\nThe reduce expression consists of pure function. Both evaluations terminate. Below is an example showing how \u0026lsquo;impure\u0026rsquo; function evaluates to different results in applicative order and normal order.\nimport scala.util.Random def squareA(x: Int): Int = x * x def squareN(x: =\u0026gt; Int): Int = x * x @main def main = { val rnd = new Random() val resultA = squareA(rnd.nextInt(10)) println(s\u0026#34;Got a square(A) value: $resultA\u0026#34;) // below evaluates to `rnd.nextInt(10) * rnd.nextInt(10)` val resultN = squareN(rnd.nextInt(10)) println(s\u0026#34;Got a square(N) value: $resultN\u0026#34;) } Normal order evaluation does NOT do unnecessary evaluation.\n// define infinite loop def loop: Boolean = loop // define normal order function def constOne(x: Int, y: =\u0026gt; Boolean) = 1 // y follows normal order evaluation, so it is NOT evaluated at all. constOne(2, loop) // won\u0026#39;t trigger infinite loop Scala uses short-circuit evaluation for \u0026amp;\u0026amp; and ||. As a result, true || loop terminates.\nRecursion Recursion is widely used in FP, but it prevents the compiler from inferring the function\u0026rsquo;s return type, requiring an explicit type declaration in the function signature.\ndef fact(n: Int): Int = require(n \u0026gt;= 0, \u0026#34;parameter should NOT be negative\u0026#34;) if n == 0 then 1 else n * fact(n-1) Tail Recursion Tail recursion is supported in Scala. The @tailrec annotation is more like a reminder, because even if the annotation is not present, the Scala compiler will still try to make recursion tail-recursive whenever possible\ndef fact(n: Int): Int = require(n \u0026gt;= 0, \u0026#34;parameter should NOT be negative\u0026#34;) @tailrec def factTR(curr: Int, accu: Int): Int = if curr == 0 then accu else factTR(curr-1, accu*curr) factTR(n, 1) Higher Order Function Functions are treated as first-class citizens in Scala. That means functions can be parameters and return values of functions. Functions that take other functions as parameters or return functions as return values are called higher order functions(HOF).\nBut why does that matter? HOF provides a flexible way to compose functions.\ndef sumOfSquare(lowerBound: Int, upperBound: Int): Int = require(lowerBound \u0026lt;= upperBound, \u0026#34;Lower bound should NOT exceed upper bound.\u0026#34;) if lowerBound == upperBound then lowerBound*lowerBound else lowerBound*lowerBound + sumOfSquare(lowerBound+1, upperBound) def sumOfDouble(lowerBound: Int, upperBound: Int): Int = require(lowerBound \u0026lt;= upperBound, \u0026#34;Lower bound should NOT exceed upper bound.\u0026#34;) if lowerBound == upperBound then upperBound*2 else lowerBound*2 + sumOfDouble(lowerBound+1, upperBound) The behavior of above two functions is very similar to each other. We can \u0026rsquo;extract\u0026rsquo; the different part, and introduce a function to \u0026lsquo;abstract\u0026rsquo; the behavior like below\ndef sumOfFn(lowerBound: Int, upperBound: Int, fn: Int =\u0026gt; Int): Int = require(lowerBound \u0026lt;= upperBound, \u0026#34;Lower bound should NOT exceed upper bound.\u0026#34;) if lowerBound == upperBound then fn(lowerBound) else fn(lowerBound) + sumOfFn(lowerBound+1, upperBound, fn) # to get sum of square from 3 to 6 sumOfFn(3, 6, x =\u0026gt; x*x) There is still hard code in above implementation, the + operation. If we extract that part, and introduce a function, we actually implement the map-reduce operation.\ndef mapReduce(lowerBound: Int, upperBound: Int, map: Int =\u0026gt; Int, reduce: (Int, Int) =\u0026gt; Int): Int = require(lowerBound \u0026lt;= upperBound, \u0026#34;Lower bound should NOT exceed upper bound.\u0026#34;) if lowerBound == upperBound then map(lowerBound) else reduce(map(lowerBound), mapReduce(lowerBound+1, upperBound, map, reduce)) # sum of square from 3 to 6 mapReduce(3, 6, x =\u0026gt; x*x, (a, b) =\u0026gt; a+b) Anonymous Functions In above sumOfFn and mapReduce cases, anonymous functions are passed as arguments to the functions.\nval doubleFn = (x: Int) =\u0026gt; x * 2 Note\nTo explicitly declare the return type of function like (x: Int): Int =\u0026gt; x*2 is NOT legal. Usually the return type of the function can be inferred, but if we\u0026rsquo;d like to explicitly denote the return type, we declare it on the identifier like val cube: Int =\u0026gt; Int = x =\u0026gt; x*x*x Function Currying When invoking above mapreduce function, we need to provide all four parameters. Think about below cases\nsum of square from 3 to 5 sum of double from 2 to 6 sum of cube from 5 to 7 In above cases, the reduce function is sum, and we need to repeatedly provide it in each invocation. It would be better if we can make an \u0026lsquo;intermediate\u0026rsquo; function that is fixed to sum as reduce logic. To do that we utilize function currying.\n// mapReduce via function currying def mapReduce(reduce: (Int, Int) =\u0026gt; Int) (map: Int =\u0026gt; Int) (lowerBound: Int, upperBound: Int): Int = require(lowerBound \u0026lt;= upperBound, \u0026#34;Lower bound should NOT exceed upper bound.\u0026#34;) if lowerBound == upperBound then map(lowerBound) else reduce(map(lowerBound), mapReduce(reduce)(map)(lowerBound+1, upperBound)) // As usual we can provide all the parameters mapReduce((a: Int, b: Int)=\u0026gt;a+b) ((x:Int)=\u0026gt;x*x) (3,5) The mapReduce function has type ((Int, Int) =\u0026gt; Int) =\u0026gt; (Int =\u0026gt; Int) =\u0026gt; (Int, Int) =\u0026gt; Int, which forms a chain of functions.\nscala\u0026gt; mapReduce val res0: ((Int, Int) =\u0026gt; Int) =\u0026gt; (Int =\u0026gt; Int) =\u0026gt; (Int, Int) =\u0026gt; Int = Lambda/0x0000026581544c10@5b74902c That makes partial application available. Partial application returns an \u0026lsquo;intermediate\u0026rsquo; function. Note\nFunction types associate to the right, so Int =\u0026gt; Int =\u0026gt; Int means Int =\u0026gt; (Int =\u0026gt; Int). Function invocation assoticates to the left, so myFun(1)(2) means (myFun(1))(2). // partial application with `reduce` function provided scala\u0026gt; val sumOfFn = mapReduce((a: Int, b: Int) =\u0026gt; a+b) val sumOfFn: (Int =\u0026gt; Int) =\u0026gt; (Int, Int) =\u0026gt; Int = Lambda/0x0000026581570410@1397b141 scala\u0026gt; sumOfFn((x: Int) =\u0026gt; x*x)(3,5) val res1: Int = 50 scala\u0026gt; sumOfFn((x: Int) =\u0026gt; x*x*x)(3,5) val res2: Int = 216 // further partial applicaiton with `map` function provided scala\u0026gt; val sumOfQuad = sumOfFn((x: Int) =\u0026gt; x*4) val sumOfQuad: (Int, Int) =\u0026gt; Int = Lambda/0x0000026581572000@2bc8caa7 scala\u0026gt; sumOfQuad(3, 6) val res3: Int = 72 scala\u0026gt; sumOfQuad(5, 9) val res4: Int = 140 Object Oriented Programming with Scala Scala can be used in functional programming style, but it also supports object oriented programming well.\nSimple Introduction to Classes To define a class\nclass Robot(val robotName: String, height: Int): val robotHeight = height def introSelf(target: Robot) : String = s\u0026#34;Hello ${target.robotName}, I am ${this.robotName}\u0026#34; @main def main = val arale = new Robot(\u0026#34;Arale\u0026#34;, 139) val wallE = new Robot(\u0026#34;wallE\u0026#34;, 100) println(arale.introSelf(wallE)) println(s\u0026#34;WallE\u0026#39;s height is ${wallE.robotHeight}\u0026#34;) Note\nDon\u0026rsquo;t forget the colon : at the end of the \u0026lsquo;class definition\u0026rsquo; line. val robotName: String makes robotName a public field and available from outside of the class. Without val in front of robotName, robotName would not be treated as a filed, it\u0026rsquo;s only visible inside of the class, and target.robotName would cause syntax error. robotHeight is defined as a value, which represents one attribute of the class. this keyword represents the current object of the class. Data Abstraction with Classes Classes can be used to describe data and behavior. For example, below class represents a rational number\n// define a class without explicit class body class Rational(val numerator: Int, val denominator: Int) // try it in Scala REPL scala\u0026gt; val r1 = Rational(15, 55) val r1: Rational = Rational@a4dcede scala\u0026gt; r1.numerator val res10: Int = 15 scala\u0026gt; r1.denominator val res11: Int = 55 Explanation\nval before the class parameters (numerator and denominator) make those parameters available to the outside of the class. Otherwise, they can only be visited inside of the class. Reduction of fraction is a common operation to rational numbers.\nclass Rational(val numerator: Int, val denominator: Int): // greatest common divisor private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a%b) def numer = numerator/gcd(numerator, denominator) def denom = denominator/gcd(numerator, denominator) override def toString = s\u0026#34;$numerator/$denominator[$numer/$denom]\u0026#34; // try in REPL scala\u0026gt; val r1 = Rational(15, 55) val r1: Rational = 15/55[3/11] In above implementation, gcd is repeatedly called each time numer or denom is called. To prevent that, refine the implementation as\n// refine Rational implementation to remove duplicate call on gcd class Rational(val numerator: Int, val denominator: Int): private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a%b) private val divisor = gcd(numerator, denominator) def numer = numerator/divisor def denom = denominator/divisor override def toString = s\u0026#34;$numerator/$denominator[$numer/$denom]\u0026#34; // try in REPL scala\u0026gt; val r2 = Rational(27, 90) val r2: Rational = 27/90[3/10] From the caller perspective, it may not realize the refinement at all, because the way how it interacts with Rational remains the same. The ability to choose different implementations of the data without affecting clients is called data abstraction.\nExtension Methods Extension methods allow use toe expand the behavior of a class. Some typical cases are\nOperations that feel like they \u0026ldquo;should\u0026rdquo; be part of a type but aren\u0026rsquo;t. Domain-specific operations on thrid-party or standard library types, when we cannot modify the original class but want to add domain-specific behavior. For example, in above Rational case, suppose Rational class is defined as a standard library class. We cannot modify the \u0026ldquo;original\u0026rdquo; standard libraray code, but we can utilize extention methods to implement operations like abs, min and etc., so that we \u0026ldquo;expand\u0026rdquo; the feature of Rational.\nclass Rational(val numerator: Int, val denominator: Int): private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a%b) private val divisor = gcd(numerator, denominator) def numer = numerator/divisor def denom = denominator/divisor override def toString = s\u0026#34;[$numer/$denom]\u0026#34; extension (r: Rational) def abs: Rational = Rational(r.numer.abs, r.denom.abs) end extension // run in REPL scala\u0026gt; val myNumber = new Rational(-2, 5) val myNumber: Rational = [-2/5] scala\u0026gt; myNumber.abs val res0: Rational = [2/5] Note\nUnlike class, there is no trailing colon on extension line. r in extension(r: Rational) is known as extension parameter. Define parameterless function abs without a pair of empty parentheses to indicate no side effect. Extentions can only add new members, not override existing ones. Different people may define extensions in different modules so that they can have their own versions of enhancement that don\u0026rsquo;t run into others. Extensions of a class become available if they are\ndefined or imported in the current scope listed in the companion object Infix Notation Operators like +, - and etc. are identifiers, and they can be used as function names.\nclass Rational(val numerator: Int, val denominator: Int): private def gcd(a: Int, b: Int): Int = if b == 0 then a else gcd(b, a%b) private val divisor = gcd(numerator, denominator) def numer = numerator/divisor def denom = denominator/divisor override def toString = s\u0026#34;[$numer/$denom]\u0026#34; def +(o: Rational): Rational = new Rational(numer*o.denom+denom*o.numer, denom*o.denom) extension (r: Rational) def abs: Rational = Rational(r.numer.abs, r.denom.abs) def -(o: Rational) = new Rational(r.numer*o.denom-r.denom*o.numer, r.denom*o.denom) end extension With above + method and - method defined, they can be used as infix notation\n// run in REPL scala\u0026gt; val r1 = new Rational(2,3) val r1: Rational = [2/3] scala\u0026gt; val r2 = new Rational(1,5) val r2: Rational = [1/5] scala\u0026gt; r1 + r2 val res0: Rational = [13/15] scala\u0026gt; r1 - r2 val res1: Rational = [7/15] Explanation\nThose infix notation functions can be defined in either classes or extensions. If the identifier is not symbolic, such as min, the leading infex modifer is mandatory. extension (r: Rational) def abs: Rational = Rational(r.numer.abs, r.denom.abs) def -(o: Rational) = new Rational(r.numer*o.denom-r.denom*o.numer, r.denom*o.denom) infix def min(o: Rational) = if r.numer * o.denom \u0026lt;= o.numer * r. denom then r else o end extension Abstraction and Class Hierarchies Programming to abstractions rather than concrete implementations is a cornerstone principle behind most design patterns. In Scala, we can define abstract classes to follow that principle. Suppose we need a type to represent a set of integer numbers. We can have below abstraction.\nabstract class IntSet: def include(x: Int): IntSet def contains(x: Int): Boolean One implementation for above IntSet is binary trees. For instance, if we have the IntSet {1, 2, 4, 5}, below is the corresponding binary tree\nObviously two types of nodes are needed.\nEmpty node NonEmpty node class Empty() extends IntSet: def include(x :Int): IntSet = NonEmpty(x, Empty(), Empty()) def contains(x: Int): Boolean = false override def toString = \u0026#34;\u0026lt;E\u0026gt;\u0026#34; class NonEmpty(elem:Int, left: IntSet, right: IntSet) extends IntSet: def include(x: Int): IntSet = if x \u0026gt; elem then NonEmpty(elem, left, right.include(x)) else if x \u0026lt; elem then NonEmpty(elem, left.include(x), right) else this def contains(x: Int): Boolean = if x \u0026gt; elem then right.contains(x) else if x \u0026lt; elem then left.contains(x) else true override def toString = s\u0026#34;($elem, ${left.toString}, ${right.toString})\u0026#34; Explanation\nextends keyword denotes the subclass-superclass relationship. A class defined without extends actually extends Objct class, which is java.lang.Object. The direct or indirect superclass of a class is called base class. For instance, IntSet and Object are base classes of Empty class. override is a modifer, instead of an annotation in Scala. It denotes to override the exsiting method defined in base class. // run in REPL scala\u0026gt; val intSetA = Empty().include(1).include(4).include(5).include(2) val intSetA: IntSet = (1, \u0026lt;E\u0026gt;, (4, (2, \u0026lt;E\u0026gt;, \u0026lt;E\u0026gt;), (5, \u0026lt;E\u0026gt;, \u0026lt;E\u0026gt;))) scala\u0026gt; val intSetB = Empty().include(4).include(1).include(5).include(2) val intSetB: IntSet = (4, (1, \u0026lt;E\u0026gt;, (2, \u0026lt;E\u0026gt;, \u0026lt;E\u0026gt;)), (5, \u0026lt;E\u0026gt;, \u0026lt;E\u0026gt;)) object for Singleton In above IntSet implementation, Empty is defined as a class, meaning multiple instances of Empty will be created, and that\u0026rsquo;s a waste. To fix that defect, use object type to implement singleton.\nobject Empty extends IntSet: def include(x :Int): IntSet = NonEmpty(x, Empty, Empty) def contains(x: Int): Boolean = false override def toString = \u0026#34;\u0026lt;E\u0026gt;\u0026#34; end Empty // optional end marker Note\nEmpty now is object type and we cannot create instance of it anymore. To reference it, simply use Empty. Singleton objects are values, so Empty evaluates to itself. Companion object When a class type and an object type are defined in the same source file with the same name, they are called companions. Note\nThe companion object must be defined in the same source file as its companion class. If they are defined in separate files, they are just a regular class and a regular objct that happen to have the same name. A companion object of a class plays a role similar to static class definitions in Java.\nobject for Program Entry Point To build a standalone application/program, create an object type with main method\nobject Hello: def main(args: Array[String]): Unit = println(\u0026#34;Hello from Scala!\u0026#34;) A common best practice is to name the source file after the object it defines, so name the source file Hello.scala\nTo execute the program\nC:\\sandbox\\ScalaLab\\Scripts\u0026gt; scala run Hello.scala Compiling project (Scala 3.7.4, JVM (21)) Compiled project (Scala 3.7.4, JVM (21)) Hello from Scala! Alternatively, we can compile the source to class files using scala compile, and then run the program. Notice in Scala 3, powered by Scala CLI, a unifed scala command with subcommands like compile, run, repl, test, etc. are introduced. The old scalac and scala still exist for backward compatibility.\nUsing scala compile, class files are generated under .scala-build/ directory by default.\nDynamic Method Binding Method invocation depends on the runtime type of the object.\nclass Robot(val name: String): def greet(target: String): String = s\u0026#34;Hi $target, this is Robot[$name].\u0026#34; class CleaningRobot(name: String) extends Robot(name): override def greet(target: String): String = s\u0026#34;Greeting from CleaningRobot[$name]: Hello $target.\u0026#34; @main def main() = val wallE: Robot = new CleaningRobot(\u0026#34;Wall-E\u0026#34;) println(wallE.greet(\u0026#34;EVE\u0026#34;)) Explanation\nwallE is declared as Robot type, but assigned to an instance of CleaningRobot. The method invocation depends on the runtime type, so the greet defined in CleaningRobot is invoked. Organize Classes with Packages Classes and objects are organized in packages. To place a class/object type inside a package, use package keyword at the top of the source file. For example\npackage id.my.cxu.examples object Hello: def main(args: Array[String]): Unit = println(\u0026#34;Hello from Scala!\u0026#34;) def sayHello(target: String) = s\u0026#34;Hello, $target\u0026#34; Now compile the source file.\nC:\\sandbox\\ScalaLab\\Scripts\u0026gt; scala compile Hello.scala Compiling project (Scala 3.7.4, JVM (21)) Compiled project (Scala 3.7.4, JVM (21)) C:\\sandbox\\ScalaLab\\Scripts\u0026gt; fd -H .*Hello .scala-build/.bloop/Scripts_86c27f223b-f4d7975d63/bloop-internal-classes/main-3F_GeVW0Sx2WzkmaZblELw==/id/my/cxu/examples/Hello$.class .scala-build/.bloop/Scripts_86c27f223b-f4d7975d63/bloop-internal-classes/main-3F_GeVW0Sx2WzkmaZblELw==/id/my/cxu/examples/Hello.class .scala-build/.bloop/Scripts_86c27f223b-f4d7975d63/bloop-internal-classes/main-3F_GeVW0Sx2WzkmaZblELw==/id/my/cxu/examples/Hello.tasty .scala-build/Scripts_86c27f223b-f4d7975d63/classes/main/id/my/cxu/examples/Hello$.class .scala-build/Scripts_86c27f223b-f4d7975d63/classes/main/id/my/cxu/examples/Hello.class .scala-build/Scripts_86c27f223b-f4d7975d63/classes/main/id/my/cxu/examples/Hello.tasty Hello.scala Explanation\nOne common best practice is to construct the same directory structure for srouce file. So in above case, the Hello.scala is supposed to be put under id/my/cxu/examples, but that\u0026rsquo;s not mandatory. In above example, the \u0026lsquo;qualified name\u0026rsquo; of the Hello is id.my.cxu.examples.Hello. The compilation operation generates the directories according to the package. To reference to the type, use the qualified name including both package name and class name.\n@main def main() = val greeting = id.my.cxu.examples.Hello.sayHello(\u0026#34;Wall-E\u0026#34;) println(greeting) Execute above script file (namely lab.scala) with -cp to indicate the path of the dependent class file.\nC:\\sandbox\\ScalaLab\\Scripts\u0026gt; scala run -cp .scala-build\\Scripts_86c27f223b-f4d7975d63\\classes\\main lab.scala Compiling project (Scala 3.7.4, JVM (21)) Compiled project (Scala 3.7.4, JVM (21)) Hello, Wall-E Arguably, a better way to run the script is to provide both the entry point and the dependent files together. However, in above case, there are two \u0026rsquo;entry points\u0026rsquo;. To prove that point, use the command scala run --main-class-ls \u0026lt;scala_files\u0026gt; to list the available entry points of the scala files.\nC:\\sandbox\\ScalaLab\\Scripts\u0026gt; scala run --main-class-ls Hello.scala lab.scala Warning: setting C:\\Sandbox\\ScalaLab\\Scripts as the project root directory for this run. Compiling project (Scala 3.7.4, JVM (21)) Compiled project (Scala 3.7.4, JVM (21)) id.my.cxu.examples.Hello main Explanation: the command returns two entry points\nid.my.cxu.examples.Hello Now we need to use the qualified name, instead of simply Hello to refer to the Hello object. main The desired entry point in this case is main, so we execute the command scala run -M main Hello.scala lab.scala\nC:\\sandbox\\ScalaLab\\Scripts\u0026gt; scala run -M main Hello.scala lab.scala Warning: setting C:\\Sandbox\\ScalaLab\\Scripts as the project root directory for this run. Hello, Wall-E Explanation\n-M option denotes the \u0026rsquo;entry point\u0026rsquo;/\u0026lsquo;main class\u0026rsquo; to run. In above script case, namely that\u0026rsquo;s main. Import Classes Using the qualified name of a class seems combersome, and we can \u0026lsquo;import\u0026rsquo; the class to simplify the reference form. There are two types import\nnamed import, such as import id.my.cxu.examples.Hello to just import the Hello object wildcard import, such as import id.my.cxu.examples._ to import everything in the id.my.cxu.examples package. Notice the wildcard is underscore, NOT asterisk. We can import types from either of the following:\na package conceptually, a package is a \u0026rsquo;large\u0026rsquo; object that contains many members in several individual source files. an object So refine above script file as\n@main def main() = import id.my.cxu.examples.Hello val greeting = Hello.sayHello(\u0026#34;Arale\u0026#34;) println(greeting) Notice some items are automatically imported in every Scala program.\nmembers of package scala members of package java.lang members of the singleton object scala.Prefe That\u0026rsquo;s the reason why some types and functions seem available \u0026rsquo;natually\u0026rsquo;, for example\nInt (defined inscala.Int) Boolean (defined in scala.Boolean) Object (defined in java.lang.Object) require (defined in scala.Predef.require) assert (defined in scala.Predef.assert) Traits A class may have several natural supertypes, and that is implemented with traits in Scala. A class can inherit from\nat most one class arbitrary many traits A trait is declared like an abstract class\ntrait Movable: def move(): Unit trait Soundable: def sound: String class Robot extends Movable, Soundable: def move() = println(\u0026#34;Robot is moving\u0026#34;) def sound = \u0026#34;Robot is talking\u0026#34; @main def main = val arale = new Robot() arale.move() println(arale.sound) Explanation\nClass Robot extends two traits in above example. Class Hierarchy Explanation\nscala.AnyVal is the super type of all the primitive types. scala.AnyRef is the synonym of java.lang.Object, and it\u0026rsquo;s the super type of the user defined class. Root type of the type system is scala.Any. Methods like ==, !=, equals, hashCode, toString are defined there. Bottom type of the type system is scala.Nothing. Nothing is the subtype of every other type. Nothing is a type, and there is no value of type Nothing, so we cannot assign Nothing to an identifier. Use case of Nothing type: as an element type of empty collections. Notice two types of arrows Solid line arrows: conform to the super class Dashed line arrows: implicit conversion A Tricky Type Question Given below expression, what\u0026rsquo;s the type of it?\nif true then 1 else false In Scala 2, we get the common supertype of Int and Boolean, so we end up getting type AnyVal. However in Scala 3, we get Int | Boolean, the union type, which represents a value that can be either an Int or Boolean.\nBy design, the compiler does NOT look into the condition, although it\u0026rsquo;s even fixed as true.\nPolymorphism Polymorphism is one of the pillars of OOP. To help understand and remember the word\npoly: it means many morphism: it means forms In programming, polymorphism means\nthe identify of a certain instance can be viewed as many different types the function can be applied to arguments of many types class Robot(val name: String, val model: String): def move(): Unit = println(\u0026#34;Robot is moving...\u0026#34;) class HumanoidRobot(name: String, model: String) extends Robot(name, model): def talk(): Unit = println(s\u0026#34;HumanoidRobot[$name] is talking...\u0026#34;) class Arale(robotID: String) extends HumanoidRobot(\u0026#34;Arale-\u0026#34;+robotID, \u0026#34;Arale\u0026#34;): override def move(): Unit = println(s\u0026#34;$name is running...\u0026#34;) @main def main() = val a01: Robot = new Arale(\u0026#34;a01\u0026#34;) a01.move() val a02: HumanoidRobot = new Arale(\u0026#34;a02\u0026#34;) a02.talk() a02.move() // running above program and we get scala run PolyLab.scala Compiling project (Scala 3.7.4, JVM (21)) Compiled project (Scala 3.7.4, JVM (21)) Arale-a01 is running... HumanoidRobot[Arale-a02] is talking... Arale-a02 is running... Explanation\na01 is declared as type Robot, but in fact it is an instance of Arale. Due to dynammic method binding, the effective method for a01.move() invocation is the one defined in Arale. a02 is an instance of Arale, and it can be recognized as an instance of HumanoindRobot as well, so a02.talk() works. Generics Suppose we need to implement the linked list data structure. Below is a simple design\nNil to represent the empty list Cons to represent a cell containing an element, and the remainder (tail) of the list For a list like List(1, 2, 3), above design looks like\nThe implementation can be\nimport java.{util =\u0026gt; ju} trait IntList: def isEmpty: Boolean def head: Int def tail: IntList class Cons(val head: Int, val tail: IntList) extends IntList: def isEmpty: Boolean = false override def toString = s\u0026#34;$head | ${tail.toString}\u0026#34; object Nil extends IntList: def isEmpty = true def head = throw new ju.NoSuchElementException() def tail = throw new ju.NoSuchElementException() override def toString = \u0026#34;\u0026lt;Nil\u0026gt;\u0026#34; @main def main() = val myList = Cons(1, Cons(2, Cons(3, Nil))) println(myList) // execute and we get scala run IntList.scala Compiling project (Scala 3.7.4, JVM (21)) Compiled project (Scala 3.7.4, JVM (21)) 1 | 2 | 3 | \u0026lt;Nil\u0026gt; Explanation\nval head: Int and val tail: IntList of Cons fulfill the head and tail methods defined in IntList. To create multiple instances of Nil does NOT make a lot of sense, so define it as object type. It\u0026rsquo;s easy to create types like BooleanList, StringList, etc. In fact, the only difference among those types is the type of the inner stored elements. To \u0026lsquo;generalize the type\u0026rsquo;, we can use Type Parameters.\nimport java.{util =\u0026gt; ju} trait MyList[+T]: def isEmpty: Boolean def head: T def tail: MyList[T] class Cons[T](val head: T, val tail: MyList[T]) extends MyList[T]: def isEmpty: Boolean = false override def toString = s\u0026#34;$head | ${tail.toString}\u0026#34; object Nil extends MyList[Nothing]: def isEmpty = true def head = throw new ju.NoSuchElementException() def tail = throw new ju.NoSuchElementException() override def toString = \u0026#34;\u0026lt;Nil\u0026gt;\u0026#34; @main def main() = val myNumList: MyList[Int] = Cons(1, Cons(2, Cons(3, Nil))) println(myNumList) val myStrList : MyList[String] = Cons(\u0026#34;Hello\u0026#34;, Cons(\u0026#34;From\u0026#34;, Cons(\u0026#34;Scala\u0026#34;, Nil))) println(myStrList) Explanation\nThe plus sign in MyList[+T] makes MyList covariant, which means an instance of MyList[DerivedType] can be considered as an instance of MyList[BaseType]. object Nil extends MyList[Nothing] makes Nil to be an sub-type of MyList[Nothing], which in turn makes Nil an sub-type of any MyList[+T]. Technically, object Nil extends MyList also works, but explicitly denoting Nothing makes the type relationship crystal clear. Objects for OOP In pure object-oriented programming language, every value is an object. To understand the point, let\u0026rsquo;s follow this idea to create an ideal version of Boolean type and values.\nabstract class IdealBoolean: def ifThenElse[T](t: =\u0026gt; T, e: =\u0026gt; T): T def \u0026amp;\u0026amp;(x: =\u0026gt; IdealBoolean): IdealBoolean = ifThenElse(x, IdealFalse) def ||(x: =\u0026gt; IdealBoolean): IdealBoolean = ifThenElse(IdealTrue, x) def unary_! : IdealBoolean = ifThenElse(IdealFalse, IdealTrue) def ==(x: =\u0026gt; IdealBoolean): IdealBoolean = ifThenElse(x, x.unary_!) def !=(x: =\u0026gt; IdealBoolean): IdealBoolean = ifThenElse(x.unary_!, x) object IdealTrue extends IdealBoolean: def ifThenElse[T](t: =\u0026gt; T, e: =\u0026gt; T): T = t object IdealFalse extends IdealBoolean: def ifThenElse[T](t: =\u0026gt; T, e: =\u0026gt; T): T = e Explanation\nIdealBoolean is an abstract class defining the behavior of the type. There are only two possible values for IdealBoolean type, IdealTrue and IdealFalse. It doesn\u0026rsquo;t make a lot of sense to create different instances of IdealTrue and IdealFalse, so they are defined as object Functions as Objects In Scala, a function can be treated as an object. The function type A =\u0026gt; B is just an abbreviation of the type scala.Function[A, B], just as below\npackage scala trait Function1[A, B]: def apply(x: A): B Explanation\nFunctions are objects with apply methods. The name of the trait in above example is Function1, and there are traits named as Function2, Function3, etc., for functions which have more parameters. Let\u0026rsquo;s create equivalent integer sum functions using anonymous function and Function2 trait\n@main def main() = val intSumFn: (Int, Int) =\u0026gt; Int = (a, b) =\u0026gt; a + b val intSumFn2: Function2[Int, Int, Int] = new Function2[Int, Int, Int]: def apply(a: Int, b: Int): Int = a + b val cmpr = intSumFn(2,3) == intSumFn2(2,3) println(cmpr) // true println(intSumFn == intSumFn2) // false Explanation\nFunciton2 is an trait, so it\u0026rsquo;s impossible to create a \u0026lsquo;direct\u0026rsquo; instance. As a result, \u0026lsquo;anonymous class\u0026rsquo; is used to create an \u0026lsquo;indirect\u0026rsquo; instance of Function2. Although logically intSumFn and intSumFn2 are the same thing, they are not equivalent. With above concept in mind, we now have two ways to invoke a function\nregular function call such as fn(args) using apply like fn.apply(args) @main def main() = val intSumFn: (Int, Int) =\u0026gt; Int = (a, b) =\u0026gt; a + b println(intSumFn(5, 91)) println(intSumFn.apply(5, 91)) def intSumFn3(a: Int, b:Int): Int = a + b println(intSumFn3(5, 23)) println(intSumFn3.apply(5, 23)) Exaplantion\nintSumFn is an anonymous function and intSumFn3 is a regular function. We can use either regular function call or the apply function of the function object. OO Decomposition vs Pattern Matching Suppose we need to build a dummy interpretor and for demo purpose, let\u0026rsquo;s consider below entities with integer nubmers for now\nExpr: the abstract type to represent expressions, and for now only consider integer numbers Number: to represent numbers, and for demo purpose only consider integer numbers for now Sum: to represent addition operation, and for now only consider integer numbers OO Decomposition One solution to build the interpretor is like below, via OO decomposition\ntrait Expr: def eval: Int class Number(n: Int) extends Expr: def eval: Int = n class Sum(left: Expr, right: Expr) extends Expr: def eval: Int = left.eval + right.eval @main def main() = val e1 = Number(2) val e2 = Sum(Number(2), Number(5)) val r = Sum(e1, e2).eval print(r) Explanation\nExpr is defined as a trait and it is abstract type. Number contains both data(n) and operation(eval). Similar to Number, Sum contains both data(left and right) and operation(eval) But why is it called OO decomposition, given the fact that data is not separated from opertions?\n\u0026ldquo;Decomposition\u0026rdquo; here means to decompose a complex system into cohesive units with clear responsibilities. So in OO territory, it\u0026rsquo;s the idea of breaking a system down into objects, each representing a concept in the problem domain. An object encapsulates both state(data) and behavior(operations). Above implementation decompose the problem domain into Expr trait, Number class and Sum class. The responsiblity is clear, so to some extent that\u0026rsquo;s a good design. Suppose we need to add new entities/data such as Prod to represent multiplication, it\u0026rsquo;s still quite easy.\nclass Prod(left: Expr, right: Expr) extends Expr: def eval: Int = left.eval * right.eval However if we need to add a new behavior, such as a display method, now that\u0026rsquo;s a problem, because every type needs to be updated.\ntrait Expr: def eval: Int def display: String class Number(n: Int) extends Expr: def eval: Int = n def display: String = n.toString class Sum(left: Expr, right: Expr) extends Expr: def eval: Int = left.eval + right.eval def display: String = s\u0026#34;${left.display} + ${right.display}\u0026#34; @main def main() = val e1 = new Number(2) val e2 = new Sum(new Number(2), new Number(5)) val e = new Sum(e1, e2) print(s\u0026#34;${e.display} = ${e.eval}\u0026#34;) Note: using OO decomposition, it\u0026rsquo;s\neasy to add new kinds of entities/data hard to add new operations FP Decomposition - Pattern Matching Another approach to implement the interpretor is to utilize pattern matching.\ntrait Expr case class Number(n: Int) extends Expr case class Sum(left: Expr, right: Expr) extends Expr def eval(expr: Expr): Int = expr match case Number(n: Int) =\u0026gt; n case Sum(left: Expr, right: Expr) =\u0026gt; eval(left) + eval(right) @main def main() = val e1 = Number(91) val e2 = Sum(Number(2), Number(3)) val e = Sum(e1, e2) println(s\u0026#34;${eval(e)}\u0026#34;) Explanation\nWe use case classes to represent numbers and operations, because classes have below \u0026lsquo;automatic\u0026rsquo; features immutability by default: all constuctor parameters are automatically val fields (public and immutable). proper equals() and hashCode(): automatically get proper euqals() and hashCode() to compare based on field values, not object identity. pattern matching support: can be used in pattern matching with automatically generated unapply method. companion object: automatically get a companion object with an apply method, so new keyword is not needed in instantiation. copy method: automatically get a copy() method that creates modified copies while keeping other fields the same to support working with immutable data. toString implementation: automatically get a readable toString implementation showing class name and field values. Pattern matching in above example match is preceded by a selector expression, in above case expr. A MatchError is thrown if no pattern matches the value of the selector expression. Patterns are constructured from constructors, e.g. Number(n) variables, e.g. n, left wildcard patterns, e.g. _ to match anything but don\u0026rsquo;t care about the value constants, e.g. 1, true type test, e.g. n: Int some ruels and conventions of patterns Variables always begin with a lowercase letter. Names of constants begin with a Capital letter, with the exception of the reserved words like null, true, false. The same variable name can only appear once in a pattern, so Sum(x, x) is NOT a valid pattern. Now think about below bullets\nMove eval function into Expr trait to from a eval method. What if we\u0026rsquo;d like to add a display behavior? What if we\u0026rsquo;d like to add a Prod calculation? trait Expr: def eval: Int = this match case Number(n: Int) =\u0026gt; n case Sum(left: Expr, right: Expr) =\u0026gt; left.eval + right.eval def display: String = this match case Number(n) =\u0026gt; n.toString case Sum(l, r) =\u0026gt; s\u0026#34;${l.display} + ${r.display}\u0026#34; case class Number(n: Int) extends Expr case class Sum(left: Expr, right: Expr) extends Expr @main def main() = val e1 = Number(91) val e2 = Sum(Number(2), Number(3)) val e = Sum(e1, e2) println(s\u0026#34;${e.display} = ${e.eval}\u0026#34;) Explanation\neval is transformed from a function to a method of Expr trait. Instead of accepting the expr parameter for pattern matching, it use the current instance, namely this, directly. display is added to Expr trait as another method. It also utilizes pattern matching technique. It\u0026rsquo;s not that difficult to implement the first two bullets. However, to add additional data/entities, in this case Prod, all the pattern matching logic needs to be updated to \u0026lsquo;include\u0026rsquo; that new data.\ntrait Expr: def eval: Int = this match case Number(n: Int) =\u0026gt; n case Sum(left: Expr, right: Expr) =\u0026gt; left.eval + right.eval case Prod(left: Expr, right: Expr) =\u0026gt; left.eval * right.eval def display: String = this match case Number(n) =\u0026gt; n.toString case Sum(l, r) =\u0026gt; s\u0026#34;${l.display} + ${r.display}\u0026#34; case Prod(l:Sum, r:Sum) =\u0026gt; s\u0026#34;(${l.display}) * (${r.display})\u0026#34; case Prod(l, r:Sum) =\u0026gt; s\u0026#34;${l.display} * (${r.display})\u0026#34; case Prod(l:Sum, r) =\u0026gt; s\u0026#34;(${l.display}) * ${r.display}\u0026#34; case Prod(l, r) =\u0026gt; s\u0026#34;${l.display} * ${r.display}\u0026#34; case class Number(n: Int) extends Expr case class Sum(left: Expr, right: Expr) extends Expr case class Prod(left: Expr, right: Expr) extends Expr @main def main() = val e1 = Number(91) val e2 = Sum(Number(2), Number(3)) val e = Prod(e1, e2) println(s\u0026#34;${e.display} = ${e.eval}\u0026#34;) Note: using FP decomposition, pattern matching, it\u0026rsquo;s\neasy to add new operations hard to add new kinds of entities/data Back to the implementation. The 4 adjacent Prod patterns look a little awkard. To improve the implementation, we can do a further pattern matching like below\ntrait Expr: def eval: Int = this match case Number(n: Int) =\u0026gt; n case Sum(left: Expr, right: Expr) =\u0026gt; left.eval + right.eval case Prod(left: Expr, right: Expr) =\u0026gt; left.eval * right.eval def display: String = this match case Number(n) =\u0026gt; n.toString case Sum(l, r) =\u0026gt; s\u0026#34;${l.display} + ${r.display}\u0026#34; case Prod(l, r) =\u0026gt; s\u0026#34;${{addParenthesesIfNecessary(l)}} * ${{addParenthesesIfNecessary(r)}}\u0026#34; case class Number(n: Int) extends Expr case class Sum(left: Expr, right: Expr) extends Expr case class Prod(left: Expr, right: Expr) extends Expr def addParenthesesIfNecessary(e: Expr): String = e match case Sum(l, r) =\u0026gt; s\u0026#34;(${l.display} + ${r.display})\u0026#34; case _ =\u0026gt; e.display @main def main() = val e1 = Number(91) val e2 = Sum(Number(2), Number(3)) val e = Prod(e2, e1) println(s\u0026#34;${e.display} = ${e.eval}\u0026#34;) Expalnation\naddParenthesesIfNecessary adds parentheses to Sum expression. _ pattern matches to any value. Types to Support Pattern Matching A list looks like an array, but there are two important fundamental differences\nLists are immutable Lists are recursive It\u0026rsquo;s easy to understand \u0026lsquo;immutable\u0026rsquo; mentioned above, but what does \u0026lsquo;recursive\u0026rsquo; mean? A non-empty list consists of an element and a sub list. The cons operator : separates the head(element) and the tail(sub list) of a list. For example\n@main def main() = val l1 = List(\u0026#34;US\u0026#34;, \u0026#34;CN\u0026#34;, \u0026#34;RU\u0026#34;) val l2 = \u0026#34;US\u0026#34; :: \u0026#34;CN\u0026#34; :: \u0026#34;RU\u0026#34; :: Nil println(l1 == l2) // true println(l1 eq l2) // false println(Nil == List()) //true Explanation\nl1 and l2 are two equivalent lists, but they are indeed two different objects. == tests the value equality and eq tests the reference equality. Nil is simply an empty list. When using :: to construct lists, do not forget to put Nil at the end of the list to indicate there is no more sub list. Below are the three commonly used methods of lists\nhead: to get the first element of the list tail: to get the sub list isEmpty: to return true if the list is empty, and otherwise false List can be effectively used in pattern matching with understanding the underlying structure. Below are some examples\n1 :: 2 :: xs: the list starting with element 1 and 2 x: Nil: the list with one element List(x): same as above x :: Nil List(): the empty list, same as Nil List(2 :: xs): the list containing another list as its element, and the inner list starts with element 2 Utilize pattern matching to implement the insertion sort of lists\ndef insertionSort(l: List[Int]): List[Int] = l match case Nil =\u0026gt; Nil case h :: Nil =\u0026gt; l case h :: t =\u0026gt; val sortedTail = insertionSort(t) if h \u0026gt; sortedTail.head then sortedTail.head :: insertionSort(h :: sortedTail.tail) else h :: sortedTail @main def main() = val l = List(5, 8, 2, 9, 7, 3) val sortedL = insertionSort(l) println(sortedL) Exaplanation\ninsertionSort is a recursive function so the return type should be declared explicitly. Lists are immutable so the sorted list is a new list. ","date":"2025-12-07T21:35:08+08:00","permalink":"https://blog.cxu.my.id/post/programming-languages/scala/quick-guide/","title":"Scala Quick Guide"},{"content":"Get Help \u0026gt;\u0026gt;\u0026gt; help(str) Help on class str in module builtins: \u0026gt;\u0026gt;\u0026gt; help(str.find) Help on method_descriptor: find(...) S.find(sub[, start[, end]]) -\u0026gt; int Return the lowest index in S where substring sub is found, such that sub is contained within S[start:end]. Optional arguments start and end are interpreted as in slice notation. Return -1 on failure. Collection Lists, Tuples, Sets and Dictionaries Summary\nLists: containers to hold multiple elements in order Tuples: similar to lists, but immutable Sets: containers to hold multiple element when membership instead of order or position is important Dictionaries: key-value pairs List highlights\n# a list can hold elements of different types \u0026gt;\u0026gt;\u0026gt; x = [1, 2, 3, \u0026#34;abc\u0026#34;, [4, 5]] # slicing is a widely used operation # [start index: end index: step] \u0026gt;\u0026gt;\u0026gt; x[3:1:-1] [\u0026#39;abc\u0026#39;, 3] # perform in place modification with slicing \u0026gt;\u0026gt;\u0026gt; x = [1, 2, 3, \u0026#34;abc\u0026#34;, [4, 5]] \u0026gt;\u0026gt;\u0026gt; x[3:] = [4] \u0026gt;\u0026gt;\u0026gt; x [1, 2, 3, 4] # in place \u0026#39;filtering\u0026#39; \u0026gt;\u0026gt;\u0026gt; x[:] = [e for e in x if e % 2 == 0] \u0026gt;\u0026gt;\u0026gt; x [2, 4] # in-place sort vs. returning a sorted list # in-place sort \u0026gt;\u0026gt;\u0026gt; countries = [\u0026#34;China\u0026#34;, \u0026#34;USA\u0026#34;, \u0026#34;Australia\u0026#34;] \u0026gt;\u0026gt;\u0026gt; countries.sort(key=lambda x: len(x)) \u0026gt;\u0026gt;\u0026gt; countries [\u0026#39;USA\u0026#39;, \u0026#39;China\u0026#39;, \u0026#39;Australia\u0026#39;] # sorted built-in function returns a sorted list \u0026gt;\u0026gt;\u0026gt; countries = [\u0026#34;China\u0026#34;, \u0026#34;USA\u0026#34;, \u0026#34;Australia\u0026#34;] \u0026gt;\u0026gt;\u0026gt; sorted(countries, key=lambda x: len(x)) [\u0026#39;USA\u0026#39;, \u0026#39;China\u0026#39;, \u0026#39;Australia\u0026#39;] # shallow copy vs. deep copy \u0026gt;\u0026gt;\u0026gt; l1 = [[\u0026#34;x\u0026#34;], \u0026#34;y\u0026#34;] # shallow copy via slicing \u0026gt;\u0026gt;\u0026gt; l1_sc = l1[:] # deep copy \u0026gt;\u0026gt;\u0026gt; import copy \u0026gt;\u0026gt;\u0026gt; l1_dc = copy.deepcopy(l1) Tuple highlights\n# `,` is needed for single element tuple \u0026gt;\u0026gt;\u0026gt; type((1,)) \u0026lt;class \u0026#39;tuple\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; type((1)) \u0026lt;class \u0026#39;int\u0026#39;\u0026gt; # tuple may be immutable, but NOT hashable \u0026gt;\u0026gt;\u0026gt; x = (1,2,[3]) \u0026gt;\u0026gt;\u0026gt; type(x) \u0026lt;class \u0026#39;tuple\u0026#39;\u0026gt; # tuple itself is immutable, but its content may be mutable \u0026gt;\u0026gt;\u0026gt; x[2].extend([4,5]) \u0026gt;\u0026gt;\u0026gt; x (1, 2, [3, 4, 5]) # swap variable values with tuple and packing/unpacking \u0026gt;\u0026gt;\u0026gt; x = 5 \u0026gt;\u0026gt;\u0026gt; y = 23 \u0026gt;\u0026gt;\u0026gt; x,y = y,x # y,x is packed into a tuple and then unpacked for assignment \u0026gt;\u0026gt;\u0026gt; x 23 \u0026gt;\u0026gt;\u0026gt; y 5 Set highlights\n# items in a set must be both immutable and hashable \u0026gt;\u0026gt;\u0026gt; set((1,2,3)) {1, 2, 3} \u0026gt;\u0026gt;\u0026gt; set((1,2,[3])) Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; TypeError: unhashable type: \u0026#39;list\u0026#39; # duplicate items are removed when adding to set \u0026gt;\u0026gt;\u0026gt; s = {1,2,3,4,5,2,3} \u0026gt;\u0026gt;\u0026gt; s {1, 2, 3, 4, 5} \u0026gt;\u0026gt;\u0026gt; s.add(5) \u0026gt;\u0026gt;\u0026gt; s {1, 2, 3, 4, 5} # a set itself is not immutable and hashable # to put a set inside another set, use frozenset \u0026gt;\u0026gt;\u0026gt; {1,2,3,{4,5}} Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; TypeError: unhashable type: \u0026#39;set\u0026#39; \u0026gt;\u0026gt;\u0026gt; {1,2,3,frozenset({4,5})} {frozenset({4, 5}), 1, 2, 3} Dictionary highlights\n# widely used `items` function \u0026gt;\u0026gt;\u0026gt; for k,v in {\u0026#34;China\u0026#34;: 5, \u0026#34;USA\u0026#34;: 3}.items(): ... print(f\u0026#34;{k} --\u0026gt; {v}\u0026#34;) ... China --\u0026gt; 5 USA --\u0026gt; 3 # to delete an entry, use `del` \u0026gt;\u0026gt;\u0026gt; d = {\u0026#34;China\u0026#34;:5, \u0026#34;USA\u0026#34;:3} \u0026gt;\u0026gt;\u0026gt; del d[\u0026#34;USA\u0026#34;] \u0026gt;\u0026gt;\u0026gt; d {\u0026#39;China\u0026#39;: 5} # provide default value when the key does NOT exist in the dict # `dict.get(key, dflt_val)` # `dict.setdefault(key, dflt_val)` \u0026gt;\u0026gt;\u0026gt; d = {\u0026#34;China\u0026#34;:5, \u0026#34;USA\u0026#34;:3} \u0026gt;\u0026gt;\u0026gt; d.get(\u0026#34;Japan\u0026#34;, 5) 5 \u0026gt;\u0026gt;\u0026gt; d.setdefault(\u0026#34;Korea\u0026#34;, 5) 5 \u0026gt;\u0026gt;\u0026gt; d[\u0026#34;Korea\u0026#34;] 5 Dictionaries can be used as caches to avoid recalculation\ncal_cache = {} def calc(param): if param not in cal_cache: # calculate and then store the result into cache result = calculate(param) cal_cache[param] = result return cal_cache[param] Comprehension Don’t loop if a comprehension can do it cleaner.\n# list comprehension \u0026gt;\u0026gt;\u0026gt; [e*e for e in [1,2,3]] [1, 4, 9] # set comprehension \u0026gt;\u0026gt;\u0026gt; {e*e for e in {1,2,3}} {1, 4, 9} # dict comprehension \u0026gt;\u0026gt;\u0026gt; {k.upper() : v*2 for k, v in {\u0026#34;a\u0026#34;:1, \u0026#34;b\u0026#34;:2}.items()} {\u0026#39;A\u0026#39;: 2, \u0026#39;B\u0026#39;: 4} Strings Strings can be treated as sequences of chars, so operations like slicing can be performed on strings.\n\u0026gt;\u0026gt;\u0026gt; \u0026#34;Hello\u0026#34;[-1::-1] \u0026#39;olleH\u0026#39; Numeric and unicode escape sequences can be used to present strings.\n\u0026gt;\u0026gt;\u0026gt; \u0026#34;\\x6D\u0026#34; \u0026#39;m\u0026#39; \u0026gt;\u0026gt;\u0026gt; \u0026#34;\\u2713\u0026#34; \u0026#39;✓\u0026#39; \u0026gt;\u0026gt;\u0026gt; \u0026#39;\\u4F60\\u597D\u0026#39; \u0026#39;你好\u0026#39; Strings are immutable so methods return new strings, although they look like updating the string contents in place.\n\u0026gt;\u0026gt;\u0026gt; \u0026#34;hello, world\u0026#34;.title() \u0026#39;Hello, World\u0026#39; \u0026gt;\u0026gt;\u0026gt; \u0026#34;C++++\u0026#34;.replace(\u0026#34;++\u0026#34;,\u0026#34;+\u0026#34;) \u0026#39;C++\u0026#39; The string module defines some useful constants.\n\u0026gt;\u0026gt;\u0026gt; import string \u0026gt;\u0026gt;\u0026gt; string.digits \u0026#39;0123456789\u0026#39; \u0026gt;\u0026gt;\u0026gt; string.hexdigits \u0026#39;0123456789abcdefABCDEF\u0026#39; \u0026gt;\u0026gt;\u0026gt; string.whitespace \u0026#39; \\t\\n\\r\\x0b\\x0c\u0026#39; \u0026gt;\u0026gt;\u0026gt; string.ascii_lowercase \u0026#39;abcdefghijklmnopqrstuvwxyz\u0026#39; Formal string representation vs. Informal string representation\nrepr: formal string representation of a Python object. The returned string representation can be used to rebuilt the original object, just like serialization/deserialization. It\u0026rsquo;s great for debugging programs. str: informal string representation of a Python object. It\u0026rsquo;s intended to be read by humans. str applied to any built-in Python object always call repr \u0026gt;\u0026gt;\u0026gt; repr([1,2,3]) \u0026#39;[1, 2, 3]\u0026#39; \u0026gt;\u0026gt;\u0026gt; str([1,2,3]) \u0026#39;[1, 2, 3]\u0026#39; String interpolation is available since version 3.6. It\u0026rsquo;s called f-string.\n\u0026gt;\u0026gt;\u0026gt; value = 523 \u0026gt;\u0026gt;\u0026gt; f\u0026#34;The value: {value}\u0026#34; \u0026#39;The value: 523\u0026#39; # function can be called \u0026gt;\u0026gt;\u0026gt; lang = \u0026#34;go\u0026#34; \u0026gt;\u0026gt;\u0026gt; f\u0026#34;The next one: {lang.upper()}\u0026#34; \u0026#39;The next one: GO\u0026#39; Bytes String vs. Bytes\nA string object is an immutable sequence of Unicode characters. A bytes object is a sequence of integers with values from 0 to 256, mainly for dealing with binary data. Two confusing items\nunicode: a set of characters utf-8: an encoding standard, which is used to present unicode. With different encodings, unicode will be presented with different values. \u0026gt;\u0026gt;\u0026gt; c = \u0026#34;\\u2713\u0026#34; \u0026gt;\u0026gt;\u0026gt; c \u0026#39;✓\u0026#39; # try different encoding \u0026gt;\u0026gt;\u0026gt; c.encode(encoding=\u0026#39;utf-16\u0026#39;) b\u0026#34;\\xff\\xfe\\x13\u0026#39;\u0026#34; \u0026gt;\u0026gt;\u0026gt; c.encode(encoding=\u0026#39;utf-8\u0026#39;) b\u0026#39;\\xe2\\x9c\\x93\u0026#39; # encoded value back to string, and by default utf-8 is the encoding/decoding standard. \u0026gt;\u0026gt;\u0026gt; b\u0026#39;\\xe2\\x9c\\x93\u0026#39;.decode() \u0026#39;✓\u0026#39; Control Flow The \u0026rsquo;ladder\u0026rsquo; structure is like below\nif condition1: body1 elif condition2: body2 elif condition3: body3 ... elif condition(n-1): body(n-1) else: body(n) pass can be used if an empty body of if or else is neede.\nif cond: pass else: # do something else A dictionary can be used to ease the \u0026rsquo;ladder\u0026rsquo; structure.\ndef take_action_a(): # do something def take_action_b(): # do something else def take_action_c(): # do another thing func_dict = {\u0026#39;a\u0026#39;: take_action_a, \u0026#39;b\u0026#39;: take_action_b, \u0026#39;c\u0026#39;: take_action_c} # populate the desired function key, and here simple assign \u0026#39;a\u0026#39; for demo purpose desired_func_key = \u0026#39;a\u0026#39; func_dict[desired_func_key]() for Loop for loop is different from the one in \u0026lsquo;C family\u0026rsquo; programming langauges. In Python, for iterates over the values returned by any iterable object, so it\u0026rsquo;s more like an iterator, instead of a loop structure.\n\u0026gt;\u0026gt;\u0026gt; for elt in [1,2,3,4,5]: ... if elt % 2 == 0: ... print(elt) ... 2 4 Unpacking is supported by for.\n\u0026gt;\u0026gt;\u0026gt; for idx, val in enumerate([\u0026#34;A\u0026#34;, \u0026#34;B\u0026#34;, \u0026#34;C\u0026#34;]): ... print(f\u0026#34;{idx}: {val}\u0026#34;) ... 0: A 1: B 2: C range, Generator and Memory Usage When dealing with list holding large amount of elements, we may encounter the memory usage issue. Compare the memory consumption of a list and a range.\n\u0026gt;\u0026gt;\u0026gt; import sys \u0026gt;\u0026gt;\u0026gt; sys.getsizeof(list(range(1000000))) 8000056 \u0026gt;\u0026gt;\u0026gt; sys.getsizeof(range(1000000)) 48 So using range or generator can reduce the strain on memory.\n\u0026gt;\u0026gt;\u0026gt; x = list(range(1_000_000)) # using generator expression, we don\u0026#39;t have to \u0026#39;duplicate\u0026#39; the size of `x` \u0026gt;\u0026gt;\u0026gt; g = (elt * elt for elt in x) \u0026gt;\u0026gt;\u0026gt; import sys \u0026gt;\u0026gt;\u0026gt; sys.getsizeof(g) 104 Boolean Values for Conditions In Python\n0 or empty values are False. Any other values are True. Some practical terms\nValues like 0.0 and 0+0j are False. Empty String \u0026quot;\u0026quot; is False. Empty list [] is False. Empty dictionary {} is False. The special value None is always False. Some objects, such as file objects and code objects don\u0026rsquo;t have a sensible definition of 0 or empty element, so they should NOT be used in a Boolean context.\nSome boolean related operators\nin and not in to test the membership is and is not to test the identity and, or, and not to combine boolean values Operators ==/!= vs. is/is not Equality vs. Identity\n==/!=: to test the equality is/is not: to test the identity \u0026gt;\u0026gt;\u0026gt; l1 = [1,2,3] \u0026gt;\u0026gt;\u0026gt; l2 = [1,2,3] \u0026gt;\u0026gt;\u0026gt; l1 == l2 True \u0026gt;\u0026gt;\u0026gt; l1 is l2 False and and or Used in Non-Boolean Context and and or can be used in non-boolean context to \u0026lsquo;pick\u0026rsquo; the object.\nand: pick the first false object or the last object or: pick the first true object or the last object \u0026gt;\u0026gt;\u0026gt; \u0026#34;a\u0026#34; and \u0026#34;\u0026#34; and \u0026#34;c\u0026#34; \u0026#39;\u0026#39; \u0026gt;\u0026gt;\u0026gt; \u0026#34;a\u0026#34; and \u0026#34;b\u0026#34; and \u0026#34;c\u0026#34; \u0026#39;c\u0026#39; \u0026gt;\u0026gt;\u0026gt; \u0026#34;a\u0026#34; or \u0026#34;\u0026#34; or \u0026#34;c\u0026#34; \u0026#39;a\u0026#39; \u0026gt;\u0026gt;\u0026gt; \u0026#34;\u0026#34; or \u0026#34;\u0026#34; or \u0026#34;c\u0026#34; \u0026#39;c\u0026#39; Alternative to Ternary Operator ? : Some programming languages provide the ternary opeator ? : such as below javascript code snippet\nname = 1 ? \u0026#34;Yang\u0026#34; : \u0026#34;Yin\u0026#34; console.log(name) However, there is NO such ternary operator in Python. Python chooses a more readable style\n\u0026gt;\u0026gt;\u0026gt; name = \u0026#34;Yang\u0026#34; if 1 else \u0026#34;Yin\u0026#34; \u0026gt;\u0026gt;\u0026gt; print(name) Yang Functions The basic function definition is like below\n\u0026gt;\u0026gt;\u0026gt; def double(x): ... return x * 2 ... \u0026gt;\u0026gt;\u0026gt; double(5) 10 # function without paramters \u0026gt;\u0026gt;\u0026gt; def subroutine(): ... print(\u0026#34;This is subroutine\u0026#34;) ... \u0026gt;\u0026gt;\u0026gt; subroutine() This is subroutine # function without explicit return # in this case `None` is returned \u0026gt;\u0026gt;\u0026gt; def no_explicit_return(): ... print(\u0026#34;No explicit_return\u0026#34;) ... \u0026gt;\u0026gt;\u0026gt; r = no_explicit_return() No explicit_return \u0026gt;\u0026gt;\u0026gt; r is None True Parameters Three available options for function parameters\nPositional parameters Named parameters Variable numbers of parameters Named parameters help remove the ambiguity in some cases\n\u0026gt;\u0026gt;\u0026gt; def power(base, exponential): ... if exponential == 0: ... return 1 ... else: ... return base * power(base, exponential-1) ... # using named parameter we know it\u0026#39;s cube of 2, not square of 3 \u0026gt;\u0026gt;\u0026gt; power(base = 2, exponential = 3) 8 In addition, Named parameters help in the default value case\n\u0026gt;\u0026gt;\u0026gt; def greet(message=\u0026#34;Hello\u0026#34;, name=\u0026#34;world\u0026#34;): ... print(f\u0026#34;{message}, {name}\u0026#34;) ... # use the default value of `message` parameter \u0026gt;\u0026gt;\u0026gt; greet(name=\u0026#34;NYC\u0026#34;) Hello, NYC Variable numbers of parameters allow the function to handle arbitrary numbers of parameters. There is no method overloading in Python like the one in Java, and variable numbers of parameters can be used to mimic the feature. In addition, decorator pattern can be implemented with variable numbers of parameters.\ndef decrate(fn): def decorated_fn(*parameters, **key_val_pairs): print(\u0026#34;Doing decoration tasks...\u0026#34;) fn(*parameters, **key_val_pairs) print(\u0026#34;End\\n\u0026#34;) return decorated_fn def greet(message=\u0026#34;Hello\u0026#34;, name=\u0026#34;world\u0026#34;): print(f\u0026#34;{message}, {name}\u0026#34;) decorated_greet = decrate(greet) decorated_greet(\u0026#34;Hi\u0026#34;) decorated_greet(name=\u0026#34;NYC\u0026#34;) Functions as the First-class Citizens Functions can be assigned to variables, just as other values in Python.\n\u0026gt;\u0026gt;\u0026gt; def foo(): ... print(\u0026#34;foo funciton!\u0026#34;) ... \u0026gt;\u0026gt;\u0026gt; fn = foo \u0026gt;\u0026gt;\u0026gt; fn() foo funciton! Anonymous functions are implemented as lambda expressions.\n\u0026gt;\u0026gt;\u0026gt; fn = lambda: print(\u0026#34;bar function!\u0026#34;) \u0026gt;\u0026gt;\u0026gt; fn() bar function! \u0026gt;\u0026gt;\u0026gt; fn \u0026lt;function \u0026lt;lambda\u0026gt; at 0x000002E3BDD3EB90\u0026gt; High order functions are supported \u0026rsquo;natively\u0026rsquo;, since functions are first-class citizens.\n# a function can accept functions and return function def combine(outer_fn, inner_fn): def combined_fn(*parameters, **key_values): return outer_fn(inner_fn(*parameters, **key_values)) return combined_fn def square(x): return x * x dbl = lambda x: x * 2 double_of_squred = combine(dbl, square) r = double_of_squred(5) print(r) Scope: global and nonlocal Local variables vs. global variables vs. nonlocal variables\nlocal variables: variables defined in the function global variables: variabels defined outside the function nonlocal variables: variables defined in the \u0026rsquo;enclosing\u0026rsquo; scope Compare local variables and global variables.\na = 10 def foo(): # global a a = 20 print(f\u0026#34;a in foo: {a}\u0026#34;) foo() print(f\u0026#34;global a: {a}\u0026#34;) # we get below result # a in foo: 20 # global a: 10 # when `global a` is uncommented, we get below result # a in foo: 20 # global a: 20 nonlocal refers to the one defined in the enclsoing function.\na = 10 def foo(): a = 20 def bar(): nonlocal a a = 30 print(f\u0026#34;a in bar: {a}\u0026#34;) bar() print(f\u0026#34;a in foo: {a}\u0026#34;) foo() print(f\u0026#34;global a: {a}\u0026#34;) # we get below result # a in bar: 30 # a in foo: 30 # global a: 10 Generator Functions Besides generator expressions, there are generator functions to help on better memory usage.\n# generator function def gen_1_m(): i = 1 while i \u0026lt; 1_000_000: yield i i = i + 1 s = 0 for elt in gen_1_m(): s = s + elt print(s) yield from can be used to delegate the generator to another generator.\ng1 = range(1,500_000) g2 = range(500_000,1_000_000) def gen_1_m(): yield from g1 yield from g2 s = 0 for elt in gen_1_m(): s = s + elt print(s) Modules and Scoping Rules What is a module?\na file containing Python code, which defines Pythong functions or objects name of the file defines the name of the module Why use modules?\nfor better organizing source code modules help avert name-clash issue. Suppose two people both define greet function. module_a.greet module_b.greet To use a module, import it first.\n# import the built-in `math` module \u0026gt;\u0026gt;\u0026gt; import math # check the members of the module \u0026gt;\u0026gt;\u0026gt; dir(math) [\u0026#39;__doc__\u0026#39;, \u0026#39;__loader__\u0026#39;, \u0026#39;__name__\u0026#39;, \u0026#39;__package__\u0026#39;, \u0026#39;__spec__\u0026#39;, \u0026#39;acos\u0026#39;, \u0026#39;acosh\u0026#39;, \u0026#39;asin\u0026#39;, \u0026#39;asinh\u0026#39;, \u0026#39;atan\u0026#39;, \u0026#39;atan2\u0026#39;, \u0026#39;atanh\u0026#39;, \u0026#39;ceil\u0026#39;, \u0026#39;comb\u0026#39;, \u0026#39;copysign\u0026#39;, \u0026#39;cos\u0026#39;, \u0026#39;cosh\u0026#39;, \u0026#39;degrees\u0026#39;, \u0026#39;dist\u0026#39;, \u0026#39;e\u0026#39;, \u0026#39;erf\u0026#39;, \u0026#39;erfc\u0026#39;, \u0026#39;exp\u0026#39;, \u0026#39;expm1\u0026#39;, \u0026#39;fabs\u0026#39;, \u0026#39;factorial\u0026#39;, \u0026#39;floor\u0026#39;, \u0026#39;fmod\u0026#39;, \u0026#39;frexp\u0026#39;, \u0026#39;fsum\u0026#39;, \u0026#39;gamma\u0026#39;, \u0026#39;gcd\u0026#39;, \u0026#39;hypot\u0026#39;, \u0026#39;inf\u0026#39;, \u0026#39;isclose\u0026#39;, \u0026#39;isfinite\u0026#39;, \u0026#39;isinf\u0026#39;, \u0026#39;isnan\u0026#39;, \u0026#39;isqrt\u0026#39;, \u0026#39;lcm\u0026#39;, \u0026#39;ldexp\u0026#39;, \u0026#39;lgamma\u0026#39;, \u0026#39;log\u0026#39;, \u0026#39;log10\u0026#39;, \u0026#39;log1p\u0026#39;, \u0026#39;log2\u0026#39;, \u0026#39;modf\u0026#39;, \u0026#39;nan\u0026#39;, \u0026#39;nextafter\u0026#39;, \u0026#39;perm\u0026#39;, \u0026#39;pi\u0026#39;, \u0026#39;pow\u0026#39;, \u0026#39;prod\u0026#39;, \u0026#39;radians\u0026#39;, \u0026#39;remainder\u0026#39;, \u0026#39;sin\u0026#39;, \u0026#39;sinh\u0026#39;, \u0026#39;sqrt\u0026#39;, \u0026#39;tan\u0026#39;, \u0026#39;tanh\u0026#39;, \u0026#39;tau\u0026#39;, \u0026#39;trunc\u0026#39;, \u0026#39;ulp\u0026#39;] # reference to `pi`defined in `math` \u0026gt;\u0026gt;\u0026gt; math.pi 3.141592653589793 Another import form is from \u0026lt;module\u0026gt; import \u0026lt;member/*\u0026gt;\n\u0026gt;\u0026gt;\u0026gt; from math import pi \u0026gt;\u0026gt;\u0026gt; pi 3.141592653589793 # we can even import all members using `*` \u0026gt;\u0026gt;\u0026gt; from math import * \u0026gt;\u0026gt;\u0026gt; gcd \u0026lt;built-in function gcd\u0026gt; The Module Search Path To make module files available to Python to import, put it under any path entries defined in sys.path\n\u0026gt;\u0026gt;\u0026gt; import sys \u0026gt;\u0026gt;\u0026gt; sys.path [\u0026#39;\u0026#39;, \u0026#39;C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python310\\\\python310.zip\u0026#39;, \u0026#39;C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python310\\\\DLLs\u0026#39;, \u0026#39;C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python310\\\\lib\u0026#39;, \u0026#39;C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python310\u0026#39;, \u0026#39;C:\\\\Users\\\\Administrator\\\\AppData\\\\Local\\\\Programs\\\\Python\\\\Python310\\\\lib\\\\site-packages\u0026#39;] Note\nThe first module file found in the entries is used. If no desired module can be found, an ImportError exception is raised. How to define the path entries in sys.path list?\nsys.path list is initialized based on PYTHONPATH environment variable if it exists. Define .pth file to indicate the path entries, and put the .pth file under the directory defined by sys.prefix Scoping Rules and Namespaces A namespace maintains the mapping from identifiers to objects. A statement like x = 1 adds x to a namespace and associates x with the value 1.\nIn Python there are three namespaces\nlocal: holding local functions and variables global: holding module functions and module variables built-in: holding built-in functions When Python needs to \u0026rsquo;locate\u0026rsquo; the identifier, it follows below sequence\nCheck local namespace. If the identifier doesn\u0026rsquo;t exist in local namespace, check global namespace. If the identifier doesn\u0026rsquo;t exist in global namespace, check built-in namespace. If the identifier doesn\u0026rsquo;t exist in any of above, NameError occurs. When a function call is made, a local namespace is created.\ndef foo(): x = 1 print(f\u0026#34;In foo locals: {locals()}\u0026#34;) print(f\u0026#34;In foo globals: {globals()}\u0026#34;) y = 2 foo() # on global level, locals() is equivalent to globals() print(locals() == globals()) print(dir(__builtins__)) # executing above code snippet, we get In foo locals: {\u0026#39;x\u0026#39;: 1} In foo globals: {\u0026#39;__name__\u0026#39;: \u0026#39;__main__\u0026#39;, \u0026#39;__doc__\u0026#39;: None, \u0026#39;__package__\u0026#39;: None, \u0026#39;__loader__\u0026#39;: \u0026lt;_frozen_importlib_external.SourceFileLoader object at 0x0000013F1797C700\u0026gt;, \u0026#39;__spec__\u0026#39;: None, \u0026#39;__annotations__\u0026#39;: {}, \u0026#39;__builtins__\u0026#39;: \u0026lt;module \u0026#39;builtins\u0026#39; (built-in)\u0026gt;, \u0026#39;__file__\u0026#39;: \u0026#39;C:\\\\sandbox\\\\PythonLab\\\\Scripts\\\\lab.py\u0026#39;, \u0026#39;__cached__\u0026#39;: None, \u0026#39;foo\u0026#39;: \u0026lt;function foo at 0x0000013F178B3E20\u0026gt;, \u0026#39;y\u0026#39;: 2} True [\u0026#39;ArithmeticError\u0026#39;, \u0026#39;AssertionError\u0026#39;, \u0026#39;AttributeError\u0026#39;, \u0026#39;BaseException\u0026#39;, \u0026#39;BlockingIOError\u0026#39;, \u0026#39;BrokenPipeError\u0026#39;, \u0026#39;BufferError\u0026#39;, \u0026#39;BytesWarning\u0026#39;, \u0026#39;ChildProcessError\u0026#39;, \u0026#39;ConnectionAbortedError\u0026#39;, \u0026#39;ConnectionError\u0026#39;, \u0026#39;ConnectionRefusedError\u0026#39;, \u0026#39;ConnectionResetError\u0026#39;, \u0026#39;DeprecationWarning\u0026#39;, \u0026#39;EOFError\u0026#39;, \u0026#39;Ellipsis\u0026#39;, \u0026#39;EncodingWarning\u0026#39;, \u0026#39;EnvironmentError\u0026#39;, \u0026#39;Exception\u0026#39;, \u0026#39;False\u0026#39;, \u0026#39;FileExistsError\u0026#39;, \u0026#39;FileNotFoundError\u0026#39;, \u0026#39;FloatingPointError\u0026#39;, \u0026#39;FutureWarning\u0026#39;, \u0026#39;GeneratorExit\u0026#39;, \u0026#39;IOError\u0026#39;, \u0026#39;ImportError\u0026#39;, \u0026#39;ImportWarning\u0026#39;, \u0026#39;IndentationError\u0026#39;, \u0026#39;IndexError\u0026#39;, \u0026#39;InterruptedError\u0026#39;, \u0026#39;IsADirectoryError\u0026#39;, \u0026#39;KeyError\u0026#39;, \u0026#39;KeyboardInterrupt\u0026#39;, \u0026#39;LookupError\u0026#39;, \u0026#39;MemoryError\u0026#39;, \u0026#39;ModuleNotFoundError\u0026#39;, \u0026#39;NameError\u0026#39;, \u0026#39;None\u0026#39;, \u0026#39;NotADirectoryError\u0026#39;, \u0026#39;NotImplemented\u0026#39;, \u0026#39;NotImplementedError\u0026#39;, \u0026#39;OSError\u0026#39;, \u0026#39;OverflowError\u0026#39;, \u0026#39;PendingDeprecationWarning\u0026#39;, \u0026#39;PermissionError\u0026#39;, \u0026#39;ProcessLookupError\u0026#39;, \u0026#39;RecursionError\u0026#39;, \u0026#39;ReferenceError\u0026#39;, \u0026#39;ResourceWarning\u0026#39;, \u0026#39;RuntimeError\u0026#39;, \u0026#39;RuntimeWarning\u0026#39;, \u0026#39;StopAsyncIteration\u0026#39;, \u0026#39;StopIteration\u0026#39;, \u0026#39;SyntaxError\u0026#39;, \u0026#39;SyntaxWarning\u0026#39;, \u0026#39;SystemError\u0026#39;, \u0026#39;SystemExit\u0026#39;, \u0026#39;TabError\u0026#39;, \u0026#39;TimeoutError\u0026#39;, \u0026#39;True\u0026#39;, \u0026#39;TypeError\u0026#39;, \u0026#39;UnboundLocalError\u0026#39;, \u0026#39;UnicodeDecodeError\u0026#39;, \u0026#39;UnicodeEncodeError\u0026#39;, \u0026#39;UnicodeError\u0026#39;, \u0026#39;UnicodeTranslateError\u0026#39;, \u0026#39;UnicodeWarning\u0026#39;, \u0026#39;UserWarning\u0026#39;, \u0026#39;ValueError\u0026#39;, \u0026#39;Warning\u0026#39;, \u0026#39;WindowsError\u0026#39;, \u0026#39;ZeroDivisionError\u0026#39;, \u0026#39;__build_class__\u0026#39;, \u0026#39;__debug__\u0026#39;, \u0026#39;__doc__\u0026#39;, \u0026#39;__import__\u0026#39;, \u0026#39;__loader__\u0026#39;, \u0026#39;__name__\u0026#39;, \u0026#39;__package__\u0026#39;, \u0026#39;__spec__\u0026#39;, \u0026#39;abs\u0026#39;, \u0026#39;aiter\u0026#39;, \u0026#39;all\u0026#39;, \u0026#39;anext\u0026#39;, \u0026#39;any\u0026#39;, \u0026#39;ascii\u0026#39;, \u0026#39;bin\u0026#39;, \u0026#39;bool\u0026#39;, \u0026#39;breakpoint\u0026#39;, \u0026#39;bytearray\u0026#39;, \u0026#39;bytes\u0026#39;, \u0026#39;callable\u0026#39;, \u0026#39;chr\u0026#39;, \u0026#39;classmethod\u0026#39;, \u0026#39;compile\u0026#39;, \u0026#39;complex\u0026#39;, \u0026#39;copyright\u0026#39;, \u0026#39;credits\u0026#39;, \u0026#39;delattr\u0026#39;, \u0026#39;dict\u0026#39;, \u0026#39;dir\u0026#39;, \u0026#39;divmod\u0026#39;, \u0026#39;enumerate\u0026#39;, \u0026#39;eval\u0026#39;, \u0026#39;exec\u0026#39;, \u0026#39;exit\u0026#39;, \u0026#39;filter\u0026#39;, \u0026#39;float\u0026#39;, \u0026#39;format\u0026#39;, \u0026#39;frozenset\u0026#39;, \u0026#39;getattr\u0026#39;, \u0026#39;globals\u0026#39;, \u0026#39;hasattr\u0026#39;, \u0026#39;hash\u0026#39;, \u0026#39;help\u0026#39;, \u0026#39;hex\u0026#39;, \u0026#39;id\u0026#39;, \u0026#39;input\u0026#39;, \u0026#39;int\u0026#39;, \u0026#39;isinstance\u0026#39;, \u0026#39;issubclass\u0026#39;, \u0026#39;iter\u0026#39;, \u0026#39;len\u0026#39;, \u0026#39;license\u0026#39;, \u0026#39;list\u0026#39;, \u0026#39;locals\u0026#39;, \u0026#39;map\u0026#39;, \u0026#39;max\u0026#39;, \u0026#39;memoryview\u0026#39;, \u0026#39;min\u0026#39;, \u0026#39;next\u0026#39;, \u0026#39;object\u0026#39;, \u0026#39;oct\u0026#39;, \u0026#39;open\u0026#39;, \u0026#39;ord\u0026#39;, \u0026#39;pow\u0026#39;, \u0026#39;print\u0026#39;, \u0026#39;property\u0026#39;, \u0026#39;quit\u0026#39;, \u0026#39;range\u0026#39;, \u0026#39;repr\u0026#39;, \u0026#39;reversed\u0026#39;, \u0026#39;round\u0026#39;, \u0026#39;set\u0026#39;, \u0026#39;setattr\u0026#39;, \u0026#39;slice\u0026#39;, \u0026#39;sorted\u0026#39;, \u0026#39;staticmethod\u0026#39;, \u0026#39;str\u0026#39;, \u0026#39;sum\u0026#39;, \u0026#39;super\u0026#39;, \u0026#39;tuple\u0026#39;, \u0026#39;type\u0026#39;, \u0026#39;vars\u0026#39;, \u0026#39;zip\u0026#39;] Interaction between Python Program and System Combine Script and Module A Python program can be treated as a script or a module depending on the execution context. The structure below does the trick.\nif __name__ == \u0026#39;__main__\u0026#39;: main() else: # module-specific initialization code if needed When the Python file is executed as Python script, its __name__ is set to __main__.\nCommandline Arguments The arguments passed from commandline can be retrieved via sys.argv.\nimport sys def main(): print(sys.argv) main() sys.argv is a list\nThe first element is the name of the script file. The following elements are the arguments passed from commandline. PS C:\\sandbox\\PythonLab\\TempLab\u0026gt; python .\\my_sciprt.py Hello World \u0026#34;Test Script\u0026#34; [\u0026#39;.\\\\my_sciprt.py\u0026#39;, \u0026#39;Hello\u0026#39;, \u0026#39;World\u0026#39;, \u0026#39;Test Script\u0026#39;] # omit `.\\` to invoke the script file PS C:\\sandbox\\PythonLab\\TempLab\u0026gt; python my_sciprt.py Hello World \u0026#34;Test Script\u0026#34; [\u0026#39;my_sciprt.py\u0026#39;, \u0026#39;Hello\u0026#39;, \u0026#39;World\u0026#39;, \u0026#39;Test Script\u0026#39;] Use argparse module if more advanced features are needed to handle commandline arguments.\nFilesystem Interaction File Paths Path related modules\nos.path: before Python 3.5, and it\u0026rsquo;s imperative style. pathlib: since Python 3.5, and it\u0026rsquo;s OO style. os.path provides a useful abstraction layer to ease operations on filesystems. For example, file path separator may be differnt from OS to OS.\n\\ in Windows OS / in *nix OS Using os.path.sep, we don\u0026rsquo;t have to worry about the difference. As a result, program with that abstraction layer\nuse os.path.curdir NOT use . Unfortunately, there is no unified concept of root path. Think about the path types we have in Windows OS\nC:\\ means the C drive \\\\myftp\\share\\ means a UNC root path As a result, we do NOT have something like os.path.root in Python. To form a path\n# form a path with os.path os.path.join(\u0026#34;c:/\u0026#34;, \u0026#34;Sandbox\u0026#34;, \u0026#34;Temp\u0026#34;) # form a path with pathlib # note `joinpath` of Path object is an instance method pathlib.Path().joinpath(\u0026#34;c:/\u0026#34;, \u0026#34;Sandbox\u0026#34;, \u0026#34;Temp\u0026#34;) pathlib.Path() / \u0026#34;c:/\u0026#34;/ \u0026#34;Sandbox\u0026#34;/ \u0026#34;Temp\u0026#34; pathlib.Path(\u0026#34;c:/\u0026#34;) / \u0026#34;Sandbox\u0026#34;/ \u0026#34;Temp\u0026#34; Filesystem Operations Filesystem operations are performed via os module. Don\u0026rsquo;t get confused with sys module. Think \u0026lsquo;sys\u0026rsquo; as \u0026lsquo;Python System\u0026rsquo;.\n# change directory os.chdir(\u0026#34;My Target Dir\u0026#34;) # print current working directory os.getcwd() # list items in the directory # Note in windows, we may encunter PermissionError if the dir is read-only os.listdir(os.path.curdir) # get file/dir info os.path.exist(path_as_arg)) os.path.isfile(path_as_arg)) os.path.isdir(path_as_arg)) os.path.getsize(path_as_arg)) os.path.getatime(path_as_arg)) # renme file/dir os.rename(\u0026#34;original\u0026#34;, \u0026#34;target\u0026#34;) # remove a file # `remove` function cannot remove a directory os.remove(\u0026#34;file_to_be_removed\u0026#34;) # `rmdir` can remove an empty directory os.rmdir(\u0026#34;empty_dir_to_be_removed\u0026#34;) # create a directory os.mkdir(\u0026#34;dir_name\u0026#34;) os.makedirs(\u0026#34;auto_create_intermediate_dirs\u0026#34;) If OO style is preferred, use pathlib module. With pathlib we create different objects to represent different paths, so we don\u0026rsquo;t do operation like pathlib_obj.chdir(\u0026quot;Target Dir\u0026quot;)\n# create the obj representing the current dir curr_dir = pathlib.Path() # create the obj representing the specified path root_dir = pathlib.Path(\u0026#34;/\u0026#34;) # list the items in the directory for fs_item in curr_dir.iterdir(): print(fs_item) # fs_item is Path object as well # print current working directory # below two expressions return the same value # note current working directory is determined by where we started Python program and if we switched to another dir later curr_dir.cwd() root_dir.cwd() # get file/dir info path_obj.exists() path_obj.is_file() path_obj.is_dir() path_obj.stat() # rename a file or directory path_obj.rename(\u0026#34;new_name\u0026#34;) # remove a file path_obj.unlink() # remove an empty directory path_obj.rmdir() # create a directory path_obj.mkdir() # requires intermediate directories exist path_obj.mkdir(parents=True) # intermediate directories will be created automatically Utilities for Filesystem Operation os.scandir provides an easy approach to get metadata of filesystem entries under a directory.\n# use a context manager to ensure the file descriptor is released # regardless of whether the iterator is fully iterated with os.scandir(os.curdir) as my_dir: for fs_entry in my_dir: print(f\u0026#34;{fs_entry.name}: {fs_entry.stat()}\u0026#34;) glob.glob provides the globbing functionality.\nimport glob os.chdir(\u0026#34;c:/sandbox/pythonlab/scripts\u0026#34;) py_files = glob.glob(\u0026#34;*.py\u0026#34;) for py_file in py_files: print(f\u0026#34;Python File: {py_file}\u0026#34;) shutil.rmtree can remove a non-empty directory, and shutil.copyree can recursively make copies of all the files and subdirectories in a given directory.\nimport shutil shutil.rmtree(nonempty_dir_to_be_removed) shutil.copytree(src, dst) os.walk(directory, topdown=True, onerror=None, followlinks=False) traverses directory structure recursively. The function returns three things\nroot or path of the directory a list of its subdirectories (os.walk will be called on each subdir respectively) a list of its files for root, subdirs, files in os.walk(\u0026#34;Test\u0026#34;): for file in files: print(f\u0026#34;file name: {file}\u0026#34;) # remove backup directory from the recursion subdirs[:] = [e for e in subdirs if e != \u0026#34;backup\u0026#34;] print(f\u0026#34;Subdir list now is {subdirs}\u0026#34;) Note\nIf topdown is True or not present, the files in each directory are processed before moving to subdirectories. That means we have a chance to remove some subdirectories, such as .git/, .config/ from the recursion. File I/O Open and Close Files The classic open-process-close file operation is like below\nfile_obj = open(\u0026#34;c:/temp/hello.txt\u0026#34;) print(file_obj.readline()) file_obj.close() print(f\u0026#34;File closed? {file_obj.closed}\u0026#34;) Using context managers, we don\u0026rsquo;t need to explictly close the file.\nwith open(\u0026#34;c:/temp/hello.txt\u0026#34;) as file_obj: print(file_obj.readline()) Specify the mode to open file with\nr: read mode, the default mode w: write mode, data in file will be truncated before writing operation a: append mode, new data will be appended to the end of the file x: new file mode, it throws FileExistsError if the file exists already +: read and write mode t: text mode, the default mode b: binary mode, it supports random access With above modes, we have\nrt: read as text w+b: random accessing the file in binary mode with truncating the file first r+b: random accessing the file in binary mode without truncating the file first In addition, pay attention to below options when open the file\nencoding: sepcify the encoding to open the file with newline: different operating systems may use different characters as the new line character Suppose we have a txt file containing below unicode chars with utf-8 encoding\n✓💓🍁 We can specify the encoding as utf-8 when open the file\nwith open(\u0026#34;c:/temp/unicode.txt\u0026#34;, encoding=\u0026#34;utf-8\u0026#34;) as file_obj: print(file_obj.read(1)) print(file_obj.read(1)) print(file_obj.read(1)) Read and Write with TextIOWrapper In most cases, read, readline and readlines without argument are good enough to handle file reading. However, there will be some exceptional cases like\nthe file is too large the line contains too many contents there are too many lines Two approaches to tackle the issue\nprovide additional arguments to affect the amount of data being read every time use iterator to lazily load file contents # argument to affect the amount of data being read every time size_to_read = 50 with open(\u0026#34;c:/temp/the_zen_of_python.txt\u0026#34;, mode=\u0026#34;rt\u0026#34;) as file_obj: while sized_content := file_obj.read(size_to_read): print(sized_content, end=\u0026#39;\u0026#39;) # treat file object as generator # `open` returns a file object which is an iterator # `isinstance(fo, collections.abc.Iterator)` returns True with open(\u0026#34;c:/temp/the_zen_of_python.txt\u0026#34;, mode=\u0026#34;rt\u0026#34;) as file_obj: for line in file_obj: print(line, end=\u0026#34;\u0026#34;) Note\nsize parameter of readline indicates the max size of chars to read before encoutering the newline character, so we may read less than the size on some lines. hint parameter of readlines indicates the size of chars to be exceeded by reading lines, so we may read an \u0026rsquo;extra\u0026rsquo; line, just for exceeding the hint size. We perform \u0026lsquo;write\u0026rsquo; operation mainly with functions\nwrite writelines Below code snippet implements a dummy version of \u0026lsquo;copy\u0026rsquo;\n# dummy copy import os size_of_chunk = 128 source_file = os.path.join(\u0026#34;C:/\u0026#34;, \u0026#34;temp\u0026#34;, \u0026#34;the_zen_of_python.txt\u0026#34;) target_file = os.path.join(\u0026#34;C:/\u0026#34;, \u0026#34;temp\u0026#34;, \u0026#34;zen.txt\u0026#34;) # binary mode so both binary files and text files can be handled with open(source_file, \u0026#34;rb\u0026#34;) as sf_obj: with open(target_file, \u0026#34;wb\u0026#34;) as tf_obj: while content_chunk := sf_obj.read(size_of_chunk): print(\u0026#34;\u0026gt;\u0026#34;, end=\u0026#34;\u0026#34;) tf_obj.write(content_chunk) print(\u0026#34;Done\u0026#34;) Read and Write with pathlib pathlib provides OO style read/write operations. It encapsulates actions like \u0026lsquo;open\u0026rsquo; and \u0026lsquo;close\u0026rsquo;, so we don\u0026rsquo;t need to do them by ourselves. Below are the related functions\npathlib.Path.write_bytes pathlib.Path.write_text pathlib.Path.read_bytes pathlib.Path.read_text # dummy copy via pathlib\u0026#39;s OO style import pathlib source_file = pathlib.Path() / \u0026#34;C:/\u0026#34; / \u0026#34;temp\u0026#34; / \u0026#34;the_zen_of_python.txt\u0026#34; target_file = pathlib.Path() / \u0026#34;C:/\u0026#34; / \u0026#34;temp\u0026#34; / \u0026#34;zen.txt\u0026#34; target_file.write_bytes(source_file.read_bytes()) print(\u0026#34;Done using pathlib\u0026#34;) read_bytes and read_text don\u0026rsquo;t provide a paramter to specify the chunk size to read each time, and those functions read the entire file into memory. If memory-efficient is important, use the open function of the Path object to get the \u0026lsquo;file object\u0026rsquo; and then work as the classic open style\nimport pathlib chunk_size = 128 source_file = pathlib.Path() / \u0026#34;C:/\u0026#34; / \u0026#34;temp\u0026#34; / \u0026#34;the_zen_of_python.txt\u0026#34; target_file = pathlib.Path() / \u0026#34;C:/\u0026#34; / \u0026#34;temp\u0026#34; / \u0026#34;zen.txt\u0026#34; with source_file.open(mode=\u0026#34;rb\u0026#34;) as sf_obj: with target_file.open(mode=\u0026#34;wb\u0026#34;) as tf_obj: while chunk := sf_obj.read(chunk_size): tf_obj.write(chunk) print(\u0026#34;Done!\u0026#34;) File as Standard Out A file can be set as stdout, so that print function will write the content to the file instead of to the terminal.\nimport sys with open(\u0026#34;c:/temp/output.txt\u0026#34;, mode=\u0026#34;wt\u0026#34;) as of_obj: sys.stdout = of_obj print(\u0026#34;Hello\u0026#34;) print(\u0026#34;World\u0026#34;) # reset stdout back sys.stdout = sys.__stdout__ print(\u0026#34;Hi\u0026#34;) Alternative to setting sys.stdout to a file, in each print we can set the file parameter to the specified file.\nExceptions Intro The exception mechanism in Python is built around OO paradigm. An exception in Python is an object. The act of generating an exception is called raising or throwing an exception. Exceptions can be raised by\nexplcitly using raise statement in our own code any other functions The raise statement does below things\nhold the normal execution path of Python program raise an exception search for an exception handler that can deal with the exception if such a handler is found, execute it if no such handler, the program aborts with an error message In Python, error handlers are put together after the \u0026lsquo;happy path\u0026rsquo; code snippet. The classic structure is\ntry: # do something except \u0026lt;ExceptionType\u0026gt;: # handler code finally: # do something regardless of whether exception happens Multiple handlers can be \u0026lsquo;chained\u0026rsquo; to handler different exceptions. The rule of thumb is to put general excpetion handler in the end.\nl = [1,2,3] try: print(l[100]) except Exception: print(\u0026#34;Captured general excpetion!\u0026#34;) except IndexError: print(\u0026#34;Captured Index Error\u0026#34;) In above code snippet, by design Exception handler is put above IndexError handler, which leads to the fact that IndexError is completely \u0026lsquo;shadowed\u0026rsquo;.\nMost exceptions inherit from Exception class, and they have \u0026rsquo; Error\u0026rsquo; as their suffix instead of Exception, such as IndexError, FileNotFoundError, PermissionError, ValueError, etc. There are exception types, such as KeyboardInterrupt, inheriting from BaseException. That way, when users pressing Ctrl-c, the KeyboardInterrupt will NOT be captured by our handlers, and it can be passed to OS for terminating the program.\nRaise Exceptions The straight way to raise exception is to use raise statement like raise \u0026lt;ExceptionType\u0026gt;(\u0026lt;msg\u0026gt;). Below is an example raising NameError\nraise NameError(\u0026#34;For testing NameError\u0026#34;) Another way to raise exceptions is to use Python\u0026rsquo;s built-in functions/features. For example\nl = [1,2,3] print(l[100]) print(\u0026#34;End\u0026#34;) Execute above Python script, and we will get below error message\nC:\\sandbox\\PythonLab\\scripts\u0026gt; python lab.py Traceback (most recent call last): File \u0026#34;C:\\sandbox\\PythonLab\\scripts\\lab.py\u0026#34;, line 2, in \u0026lt;module\u0026gt; print(l[100]) IndexError: list index out of range In above case, l[100] raises IndexError, but there is no corresponding error handler, so the IndexError goes all the way up to Python interpretor, and there the program terminates with error message being printed out.\nIf print(\u0026quot;End\u0026quot;) is a must to us, put it in finally like below\ntry: l = [1,2,3] print(l[100]) finally: print(\u0026#34;End\u0026#34;) Execute above code, and we get below output\nC:\\sandbox\\PythonLab\\scripts\u0026gt; python lab.py End Traceback (most recent call last): File \u0026#34;C:\\sandbox\\PythonLab\\scripts\\lab.py\u0026#34;, line 3, in \u0026lt;module\u0026gt; print(l[100]) IndexError: list index out of range Explanation\nThere is no error handler, so the error will be propagated upward along the stack of functions/callers, in this case the Python interpretor. Before the execution flow pauses and goes to the caller, the finally section gets a chance to execute, and that is the reason why END gets printed out even before the error message. Catch and Handle Exception Error handlers are\nNOT for causing a program to halt with error messages perhaps for displaying error messages to users as reminders perhaps for fixing the problem in the first place With as keyword, we have the access to the exception to get detailed information. For example\ntry: raise ValueError(\u0026#34;VE-001\u0026#34;,\u0026#34;For testing purpose!\u0026#34;) except ValueError as ve: print(f\u0026#34;Code: {ve.args[0]} | Msg: {ve.args[1]}\u0026#34;) except Exception as e: print(e) finally: print(\u0026#34;Fin\u0026#34;) Execute above code, and we will get below output\nIn [3]: %run lab.py Code: VE-001 | Msg: For testing purpose! Fin Explanation\nValueError accepts *args and **kwargs. Usually, we may only pass \u0026lsquo;message\u0026rsquo;, not like above case passing both \u0026lsquo;code\u0026rsquo; and \u0026lsquo;message\u0026rsquo;. There is the handler to deal with the exception. After that, the finally section gets a chance to execute. In the handler, we can even further raise the exception. One use case is to log the error message in the handler and then raise the exception to let upward handler deal with the exception. Define Custom Exception When defining our own exceptions, use Exception as the base class, instead of BaseException. For example\n# Exception as base class class CustomError(Exception): pass try: raise CustomError(\u0026#34;This is custom err!\u0026#34;) except CustomError as ce: print(ce) raise ce except Exception as e: print(e) Execute above snippet in iPython, and we get\nIn [10]: %run lab.py This is custom err! ----------------------------------------------------------------------- CustomError Traceback (most recent call last) File C:\\Sandbox\\PythonLab\\Scripts\\lab.py:8 6 except CustomError as ce: 7 print(ce) ----\u0026gt; 8 raise ce 9 except Exception as e: 10 print(e) File C:\\Sandbox\\PythonLab\\Scripts\\lab.py:5 2 pass 4 try: ----\u0026gt; 5 raise CustomError(\u0026#34;This is custom err!\u0026#34;) 6 except CustomError as ce: 7 print(ce) CustomError: This is custom err! Custom Exception in Practice Typical useage of custom exception is\nFor small program, there will be a couple of unique exceptions, and it\u0026rsquo;s common to only create a general base exception inheriting from Exception. For large program, we may define a general base exception, and then define each unique exception inheriting from that general base exception. For example, for a Robot application. We can define class RobotError(Exception) as the base exception. Then define exceptions like class TransmissionError(RobotError), BatteryError(RobotError). Debug Program with assert assert is a special form of raise.\nassert \u0026lt;expression\u0026gt;, \u0026lt;argument\u0026gt; In practice, it can be used to debug programs.\n# business logic code to generate x\u0026#39;s value x = [1,2,3] # hard code to mimic the value assigned to x assert len(x) \u0026gt; 5, f\u0026#34;x should contain at least 5 elements, but x is {x}\u0026#34; assert in above code snippet will cause AssertionError like below\nC:\\sandbox\\PythonLab\\scripts\u0026gt; python lab.py Traceback (most recent call last): File \u0026#34;C:\\sandbox\\PythonLab\\scripts\\lab.py\u0026#34;, line 3, in \u0026lt;module\u0026gt; assert len(x) \u0026gt; 5, f\u0026#34;x should contain at least 5 elements, but x is {x}\u0026#34; AssertionError: x should contain at least 5 elements, but x is [1, 2, 3] To ignore/turn off the assert debug feature, start Python interpretor with -O option like below\npython -O lab.py That means we can safely use assert statements during development, and even leave them in the code for future use with no runtime cost.\nThe Exception Inheritance Hierarchy The except clause matters in exception handling.\n# code snippet with defect try: body except LookupError as error: exception code except IndexError as error: exception code Explanation\nIndexError is a subtype of LookupError, which means the IndexError handler is subsumed by the LookupError handler, so IndexError never gets a chance to execute. To fix the issue, simply move IndexError handler above LookupError handler. Context Managers There are situations that we follow a predictable pattern with a set \u0026lsquo;beginning\u0026rsquo; and \u0026rsquo;end\u0026rsquo;, for example when reading contents from a file\nbeginning: open the file body: operations according to the business logic end: close the file Python3 offers context managers to ease above operation. Context managers wrap a block and manage requirements on entry and departure from the block. File objects are context managers, so we can do\nwith open(filename) as infile: data = infile.read() # further operation with data # above code is logically equivalent to try: infile = open(filename) data = infile.read() # further operation with data finally: infile.close() Explanation\nUsing with, there is no need to manaully invoke file.close, becuase the file-closing operation is handled by the context manager. Context managers are great for things like\nlocking and unlocking resources committing data transactions Classes and OOP Defining Classes All the data types built into Python are classes and we can define our own classes\nclass MyClass: pass my_instance = MyClass() print(my_instance) Explanation\nWith pass we define an class with \u0026rsquo;empty\u0026rsquo; body. By convention, class identifiers are in CamelCase. To create an instance of the class type, call the class name as a function without new. Using Objcts as Structs/Records In Python, the data fields(attributes) of an object/instance doen\u0026rsquo;t need to be declared ahed of time on class level, and they can be \u0026lsquo;attached\u0026rsquo; on the fly.\nclass MyClass: pass my_instance = MyClass() # attach `field1` on the fly my_instance.field1 = \u0026#34;Hello Python\u0026#34; print(my_instance.field1) Explanation\nfield1 is attached to my_instance as an attribute/data filed/instance variable. Use dot notation to reference to the instance variable. Instance Variables and Initialization To create instance variables like what constructor does, we can use __init__ method like below\nclass Robot: def __init__(self, name): self.name = name def main(): arale = Robot(\u0026#34;Arale\u0026#34;) print(arale.name) Explanation\nself in __init__ method represents the \u0026lsquo;current\u0026rsquo; instance, so self.name refers to the instance variable. All uses of instance variables in Python require explicit mention of the containing instance. Without instance, it means the variable in the local namespace. In above example, self.name = name contains two name. The first name has the instance container self, so it refers to the instance variable, and the second name has no instance container so it is the method parameter name. Instance Methods Similar to instance variables, instance methods also \u0026rsquo;link\u0026rsquo; to instances. This is reflected by the method invocation forms\nbound invocation unbound invocation (less commonly used) class Robot: def __init__(self, name): self.name = name def greet(self, name): print(f\u0026#34;Hello {name}, I\u0026#39;m {self.name}\u0026#34;) def main(): arale = Robot(\u0026#34;Arale\u0026#34;) # bound method invocation arale.greet(\u0026#34;Dr. Slump\u0026#34;) # unbound method invocation Robot.greet(arale, \u0026#34;Dr. Slump\u0026#34;) Explanation\nAn instance method has the reference to the instance as the first parameter, and conventionally it\u0026rsquo;s named self. To invoke the instance method, we can use either bound form or unbound form. In unbound form, the instance is explicitly passed to the method as the first parameter. Think of unbound form as one function defined in the \u0026rsquo;namespace\u0026rsquo; of a class, and to refer to that function, we need to use the \u0026lsquo;qulified name\u0026rsquo;.\nclass Robot: def __init__(self, name): self.name = name def greet(self, name): print(f\u0026#34;Hello {name}, I\u0026#39;m {self.name}\u0026#34;) def main(): arale = Robot(\u0026#34;Arale\u0026#34;) fn = Robot.greet fn(arale, \u0026#34;Dr. Slump\u0026#34;) Similar to \u0026lsquo;attaching\u0026rsquo; instance variables on the fly, instance methods can be attached or overridden on the fly.\nclass Robot: def __init__(self, name): self.name = name def greet(self, name): print(f\u0026#34;Hello {name}, I\u0026#39;m {self.name}\u0026#34;) def main(): # define a function to override \u0026lt;inst\u0026gt;.greet def greet(name): print(f\u0026#34;Hi, {name}\u0026#34;) arale = Robot(\u0026#34;Arale\u0026#34;) arale.greet = greet arale.greet(\u0026#34;Dr. Slump\u0026#34;) # remove the dynamically overridden `greet` from the instance to revert back to the one defined in the class del(arale.greet) # define a function and attach to an instance def desc(): print(\u0026#34;This is Robot desc\u0026#34;) arale.desc = desc arale.desc() Explanation\nWe dynamically define two functions, greet and desc to override/attach to the instance. Note those functions do NOT have self as the first parameter. del can be used to delete the attached functions. Class Variables A class variable is a variable associated with a class, not an instance, and it\u0026rsquo;s accessible by all instances of the class.\nA class variable is created by an assignment in the class body, not via self in the __init__ method.\nclass Robot: creator = \u0026#34;Dr. Slump\u0026#34; def main(): r1 = Robot() r2 = Robot() print(f\u0026#34;R One creator: {r1.creator}\u0026#34;) print(f\u0026#34;R Two creator: {r2.creator}\u0026#34;) print(f\u0026#34;Robot creator: {Robot.creator}\u0026#34;) # change `creator` via instance `r1` and that is reflected by `r2` and `Robot`, b/c `creator` is a class variable r1.__class__.creator = \u0026#34;???\u0026#34; print(f\u0026#34;R Two creator: {r2.creator}\u0026#34;) print(f\u0026#34;Robot creator: {Robot.creator}\u0026#34;) # change `creator` via class directly Robot.creator = \u0026#34;Stark\u0026#34; print(f\u0026#34;R One creator: {r1.creator}\u0026#34;) print(f\u0026#34;R Two creator: {r2.creator}\u0026#34;) Explanation\ncreator is defined as a class variable, and it can be accessed by all instances of the class. To update the value of the class variable, we can either use \u0026lt;instance\u0026gt;.__class__.\u0026lt;class_variable\u0026gt; or use \u0026lt;class\u0026gt;.\u0026lt;class_variable\u0026gt; Class Variables as \u0026lsquo;Fallback\u0026rsquo; When accessing an instance variable, if the instance variable cannot be found, Python will try to find the class variable of the same name. If that cannot be found either, Python will signal an error.\nThis is actually what happened to the above Robot code snippet. r1.creator means to access the instance variable creator of instance r1. Howerver, there is no such instance variable, but luckily there is a class variable with the same name and it is accessed instead.\nClass Variable Trap Suppose we\u0026rsquo;d like to update the value of a class variable.\nclass Robot: creator = \u0026#34;Dr. Slump\u0026#34; def main(): r1 = Robot() # `r1.creator` below attaches an instance variable, instead of updating the class variable r1.creator = \u0026#34;???\u0026#34; print(Robot.creator) r1.__class__.creator = \u0026#34;???\u0026#34; print(Robot.creator) Note\nr1.creator = \u0026quot;???\u0026quot; attaches an instance variable, instead of updating the class variable. r1.__class__.creator refers to the class variable. Class Methods and Static Methods There are two types of class level methods in Python\nclass methods with @classmethod decorator static methods with @staticmethod decorator class Robot: creator = \u0026#34;Dr. Slump\u0026#34; @classmethod def desc_in_detail(cls): print(f\u0026#34;This is Robot created by {cls.creator}.\u0026#34;) @staticmethod def desc(): print(\u0026#34;This is Robot.\u0026#34;) # print(f\u0026#34;Access class variable with class name hardcoded: {Robot.creator}\u0026#34;) def main(): Robot.desc() Robot.desc_in_detail() Explanation\nClass methods and static methods are very similar. The difference is the first parameter of a class method is the reference to the class, conventionally named cls. As a result, it\u0026rsquo;s easy to access the class variables via that reference in class method. In the static method case, to access the class variable, hardcoding the class name is needed, such as the commented line, which is usually considered as bac code smell. When invoking class methods and static methods, use the form \u0026lt;class\u0026gt;.\u0026lt;methods\u0026gt; It\u0026rsquo;s possible to \u0026lsquo;attach\u0026rsquo; class methods and static methods on the fly.\nclass Robot: creator = \u0026#34;Dr. Slump\u0026#34; def main(): r1 = Robot() Robot.say_hello = staticmethod(lambda: print(\u0026#34;Hello from Robot.\u0026#34;)) Robot.say_hi = classmethod(lambda cls: print(f\u0026#34;Hi, I\u0026#39;m created by {cls.creator}.\u0026#34;)) r1.__class__.say_hello() r1.__class__.say_hi() # attach static method via `\u0026lt;instance\u0026gt;.__class__` r1.__class__.say_goodbye = staticmethod(lambda: print(\u0026#34;Goodbye!\u0026#34;)) Robot.say_goodbye() # attach class method via `\u0026lt;instance\u0026gt;.__class__` r1.__class__.say_bye = classmethod(lambda cls: print(f\u0026#34;Robot[by {cls.creator}] says bye\u0026#34;)) Robot.say_bye() Explanation\nTo add static methods and class methods, use the built-in staticmethod function and classmethod function correspondingly. We may ask why staticmethod and classmethod functions are needed. Without them, we are actually attaching instance methods.\nclass Robot: creator = \u0026#34;Dr. Slump\u0026#34; def __init__(self, name): self.name = name def main(): r1 = Robot(\u0026#34;Arale\u0026#34;) # add instance method as if it is defined inside the class definition r1.__class__.greet = lambda self, name: print(f\u0026#34;Hello {name}, I\u0026#39;m {self.name}.\u0026#34;) r1.greet(\u0026#34;Jarvis\u0026#34;) Robot.greet(r1, \u0026#34;Jarvis\u0026#34;) Explanation\ngreet is added as an instance method, instead of a class method or static method. Inheritance Like other object oriented programming languages, Python supports inheritance with the form SubClass(BaseClass).\nclass Robot: def __init__(self, name): self.name = name class CleaningRobot(Robot): def __init__(self, name, type): super().__init__(name) self.type = type def main(): eva = CleaningRobot(\u0026#34;Eva\u0026#34;, \u0026#34;CR-TA\u0026#34;) print(f\u0026#34;name: {eva.name} | type: {eva.type}\u0026#34;) Explanation\nCleaningRobot is defined as a subtype of Robot. super() is used in the __init__ method of the subtype to invoke the __init__ defined in the superclass to fulfill initialization. This action is not performed by default, so manually invoking the __init__ defined in the super type is needed. super is defined as a method, not a keyword like in Java. The reason is Python supports multiple inheritance, and by passing different arguments to super(), we can invoke the __init__ methods defined in different supertypes correspondingly to finish initialization. Inheritance makes the instance variables and class variables defined in the superclass accessible in the subclass.\nfrom datetime import datetime class Robot: desc = \u0026#34;Robot\u0026#34; def __init__(self, name): self.name = name self.creation_time = datetime.now() class CleaningRobot(Robot): def __init__(self, name, type): super().__init__(name) self.type = type def main(): eva = CleaningRobot(\u0026#34;Eva\u0026#34;, \u0026#34;CR-TA\u0026#34;) # `creation_time` is defined in super class print(eva.creation_time) # `desc` is the class variable of super class print(eva.desc) # \u0026#39;intercept\u0026#39; `desc` by \u0026#39;attaching\u0026#39; `desc` on `CleaningRobot` class CleaningRobot.desc = \u0026#34;Cleaning Robot\u0026#34; print(eva.desc) # further \u0026#39;intercept\u0026#39; `desc` by adding instance variable `desc` eva.desc = \u0026#34;Cleaning Robot Eva[CR-TA]\u0026#34; print(eva.desc) Explanation\ncreation_time is defined in the __init__ method of super class Robot. Think about when that __init__ is executed, the self parameter represents the newly created instance. desc is defined in the super class Robot. It is like a fallback if desc cannot be found in the instance of the sub class CleaningRobot. When eva.desc is evaluated, the order of identifying desc is look for \u0026lsquo;ad-hoc\u0026rsquo; desc attribute in the instance, and fails to find one; look for the instance variable desc defined in CleaningRobot definition and fails to find one; look for the class variable desc, and finds one inherited from the super class Robot. Once we provide \u0026lsquo;ad-hoc\u0026rsquo; desc class variable to CleaningRobot or desc attribute to eva instance, Python does not need to reach out to the super class anymore, just as reflected in the last a few lines of main. Similarly, inheritance makes the instance methods defined in super class available in sub class.\nclass Robot: def __init__(self, name): self.name = name def greet(self, name): print(f\u0026#34;Hello {name}, I\u0026#39;m {self.name}.\u0026#34;) class CleaningRobot(Robot): def __init__(self, name, model): super().__init__(name) self.model = model def main(): # access `greet` defined in super class eva = CleaningRobot(\u0026#34;Eva\u0026#34;, \u0026#34;CR-TA\u0026#34;) eva.greet(\u0026#34;WallE\u0026#34;) # \u0026#39;intercept\u0026#39; the `greet` instance method on class definition level eva.__class__.greet = lambda self: print(\u0026#34;Greeting from CleaningRobot\u0026#34;) eva.greet() # \u0026#39;intercept\u0026#39; the `greet` instance method via ad-hoc function attached to instance eva.greet = lambda name: print(f\u0026#34;Hi {name}.\u0026#34;) eva.greet(\u0026#34;Jarvis\u0026#34;) # remove the ad-hoc function to \u0026#39;revert\u0026#39; back del(eva.greet) eva.greet() Explanation\nThe greet instance method is defined in the super class Robot and it is available to the instance of sub class CleaningRobot When invoking instance method eva.greet, the identification process follows below order look for ad-hoc greet on the instance level look for greet defined in the class definition look for greet defined in the super class defition Private Items A private variable or private method is one that cannot been accessed outside the methods of the class in which it is defined. Private items make it easier to read code, because they intentionally indicate that they are used internally in a class only.\nTo define private items in Python, name them as __\u0026lt;desired_name\u0026gt;. The rule is the name begins, but does NOT end, with a double underscore is private.\nclass Robot: def __init__(self, id, name): self.__id = id self.name = name class CleaningRobot(Robot): def __init__(self, id, name, type): super().__init__(id, name) self.type = type def desc(self): print(f\u0026#34;CleaningRobot[{self.name}|{self.type}]\u0026#34;) # below statement causes error print(f\u0026#34;CleaningRobot[{self.__id}|{self.name}|{self.type}]\u0026#34;) def main(): r = CleaningRobot(\u0026#34;R001\u0026#34;, \u0026#34;Eva\u0026#34;, \u0026#34;CR-TA\u0026#34;) r.desc() if __name__ == \u0026#34;__main__\u0026#34;: main() Explanation\n__id is defined as a private instance variable of Robot. It cannot be visited in the method of the sub type CleaningRobot, so the second print statement in desc method will cause error. In addition, pay attention to the error message\n\u0026#39;CleaningRobot\u0026#39; object has no attribute \u0026#39;_CleaningRobot__id\u0026#39; What the heck is _CleaningRobot__id? It\u0026rsquo;s related to Python\u0026rsquo;s philosophy on handling private items.\nPrivacy is implemented by mangling the name of the private item, so it looks like hiding the private items from accidental access. Technically, we can still access the priviate item via the mangled name. In [112]: r = CleaningRobot(\u0026#34;R001\u0026#34;, \u0026#34;Eva\u0026#34;, \u0026#34;CR-TA\u0026#34;) In [113]: dir(r) Out[113]: [\u0026#39;_Robot__id\u0026#39;, \u0026#39;__class__\u0026#39;, \u0026#39;__delattr__\u0026#39;, \u0026#39;__dict__\u0026#39;, ...] In [114]: r._Robot__id Out[114]: \u0026#39;R001\u0026#39; Properties In Python, even without getter and setter defined for instance variables in a class, we can still access those instance variables. However, Python indeed provides @property and @\u0026lt;property_name\u0026gt;.setter decorators, in case \u0026lsquo;plain access\u0026rsquo; is not sufficient.\nclass Thermometer: def __init__(self): self._temp_fahr = 0 @property def temperature(self): return (self._temp_fahr - 32) * 5 / 9 @temperature.setter def temperature(self, temperature_in_celsius): self._temp_fahr = temperature_in_celsius * 9 / 5 + 32 def main(): t = Thermometer() t.temperature = 37 print(t.temperature) # _temp_fahr is intentionally used iternally, but no name mangling happens print(t._temp_fahr) if __name__ == \u0026#34;__main__\u0026#34;: main() Explanation\n@property provides the opportunity to update the \u0026lsquo;property\u0026rsquo; to a suitable form before returning it to the caller. The leading single underscore in _temp_fahr indicates the instance variable is intentially for internal use, just as private, but no name mangling happens. One big advantage of using properies in Python is we can start from simple/plain-old instance variables and then seamlessly migrate to properties, because both use the same dot form, and from caller\u0026rsquo;s perspective, it may even not realized the change under the hood. That\u0026rsquo;s a good example of data abstraction.\nScoping and Namespace In an instance method of a class, we have access to below namespaces, just as regualr function\nlocal global built-in In addition, via instance reference, conventionally self, we have access to instance and class namespaces.\nmod_desc = \u0026#34;Robot Module\u0026#34; class Robot: def __init__(self, name): self.name = name def greet(self, name): print(locals()) print(globals()) print(f\u0026#34;From {mod_desc}:Hello {name}, I\u0026#39;m {self.name}.\u0026#34;) def main(): arale = Robot(\u0026#34;Arale\u0026#34;) arale.greet(\u0026#34;Dr. Slump\u0026#34;) if __name__ == \u0026#34;__main__\u0026#34;: main() Explanation\nlocals() reflects the local variables. In greet method, there are two local variables, self and name. globals() in greet reflects the global items, such as __name__, __builtins__, mod_desc, main, and etc. self provides the access to instance and class related items, such as self.name Note the order of identifying the items in the namespaces\nlocal namespace global namespace built-in namespace For instance/class related items\ninstance namespace class namespace superclass namespace Multiple Inheritance In Python, a class can inherit from any number of parent classes. In the multiple inheritance case, it\u0026rsquo;s fine if none of the super classes contains the methods of the same name, but inevitably, we will have the notorious diamond inheritance. Below is an example\nA / \\ B C \\ / D With above structure, we have below code\nclass A: def m(self): print(\u0026#34;m in A\u0026#34;) class B(A): def m(self): print(\u0026#34;m in B\u0026#34;) super().m() class C(A): def m(self): print(\u0026#34;m in C\u0026#34;) super().m() class D(B, C): def m(self): print(\u0026#34;m in D\u0026#34;) super().m() def main(): d = D() print(d.__class__.mro()) d.m() if __name__ == \u0026#34;__main__\u0026#34;: main() # execute above code and we get In [1]: %run lab.py [\u0026lt;class \u0026#39;__main__.D\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.B\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.C\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.A\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;object\u0026#39;\u0026gt;] m in D m in B m in C m in A Explanation\nPython uses Method Resolution Order(MRO) with the C3 lineraization algorithm to determine which method gets called. When multiple inheritance gets a little complex, check the MRO is one effective way to understand which method will get invoked. Below is a complex inheritance example\nSuppose we have above multiple inheritance hierarchy. A method named m is defined in C, F, and G. Once m is callled on an instance of A, which m method will be invoked?\nclass E: pass class F: def m(self): print(\u0026#34;m in F\u0026#34;) # super().m() class G: def m(self): print(\u0026#34;m in G\u0026#34;) class B(E, F): pass class C: def m(self): print(\u0026#34;m in C\u0026#34;) class D(G): pass class A(B, C, D): pass def main(): a = A() a.m() print(a.__class__.mro()) if __name__ == \u0026#34;__main__\u0026#34;: main() # execute above code and we get In [13]: %run lab.py m in F [\u0026lt;class \u0026#39;__main__.A\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.B\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.E\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.F\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.C\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.D\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;__main__.G\u0026#39;\u0026gt;, \u0026lt;class \u0026#39;object\u0026#39;\u0026gt;] Explanation\nAccording to MRO, the m defined in F is invoked. Generally speaking, MRO follows a depth first, left to right order. Try uncommenting the super().m() line and the m defined in C will be invoked additionally. Following the MRO, the one after F is C, so super() in m of F means C. Regular Expression The re module provides regular expression support in Python.\nSuppose we have a text file with below contents\n123 hello world lucky 523 F789E65 The End Using re we can easily capture the lines with figures.\nimport re def main(): regex = re.compile(\u0026#34;\\d\u0026#34;) in_file = open(\u0026#34;sample.txt\u0026#34;) for line in in_file: if regex.search(line): print(line, end=\u0026#34;\u0026#34;) in_file.close() if __name__ == \u0026#34;__main__\u0026#34;: main() Explanation\nregex is a compiled regular expression for better performance, using re.compile function. re.search is used in above case. If re.match was used, only 123 would be printed out, because re.match applies the regular expression to the beginning of the string. Extract Matching Part Suppose we need to do one step further, extracting the matching part. For example, to extract consecutive number characters from the file\n123 --\u0026gt; 123 hello world lucky 523 --\u0026gt; 523 F789E65 --\u0026gt; 789 and 65 The End Use below code snippet\nimport re def main(): regex = re.compile(\u0026#34;\\d+\u0026#34;) in_file = open(\u0026#34;sample.txt\u0026#34;) for line in in_file: matching_nums = regex.findall(line) output_str = f\u0026#34;{line.strip()} --\u0026gt; {\u0026#39; and \u0026#39;.join(matching_nums)}\u0026#34; if matching_nums else line.strip() print(output_str) in_file.close() if __name__ == \u0026#34;__main__\u0026#34;: main() Explanation\nre.findall returns a list containing all the matching entries. If more control is desired, re.finditer is the function to use. Raw Strings for Regular Expression In Python, regular expressions recognize special characters such as \\n for a newline, \\t for a tab, and \\\\ for a literal backslash. In addition, Python itself processes escape sequences in string literals. Together, these two layers of escaping can easily become a source of confusion.\nimport re def main(): s = \u0026#34;\\\\ten\u0026#34; # \\ten print(s) # regex = re.compile(\u0026#34;\\\\ten\u0026#34;) regex = re.compile(\u0026#34;\\\\\\\\ten\u0026#34;) print(regex.search(s)) if __name__ == \u0026#34;__main__\u0026#34;: main() Explanation\nIn re.compile(\u0026quot;\\\\ten\u0026quot;), the string literal \u0026quot;\\\\ten\u0026quot; is first processed by Python as a norma string. During this stage, escape processed, so the value ultimately passed to re.compile function is \\ten. \\t is recognized as a tab when compiling the regular expression. That\u0026rsquo;s the reason why the commented out line does NOT work. To make regular expressions easier to write and read, raw strings can be used. A raw string looks like a normal string with a leading r character. Raw strings can be used\nsingle quotation marks double quotation marks triple quotation marks to span lines Raw strings tell Python to NOT process escape sequence in this strings.\n\u0026gt;\u0026gt;\u0026gt; r\u0026#34;Hello\u0026#34; == \u0026#34;Hello\u0026#34; True \u0026gt;\u0026gt;\u0026gt; r\u0026#34;\\the\u0026#34; == \u0026#34;\\\\the\u0026#34; True \u0026gt;\u0026gt;\u0026gt; r\u0026#34;\\the\u0026#34; == \u0026#34;\\the\u0026#34; False \u0026gt;\u0026gt;\u0026gt; print(r\u0026#34;\\the\u0026#34;) \\the \u0026gt;\u0026gt;\u0026gt; print(\u0026#34;\\the\u0026#34;) he Back to above regular expression case with raw string being used\nregexp = re.compile(r\u0026#34;\\\\ten\u0026#34;) Explanation\nr\u0026quot;\\\\ten\u0026quot; makes Python not process escape sequence, so \\\\ten is passed to re.compile function. In re.compile, \\\\ means to form a backslash, so \\\\ten is \u0026rsquo;translated\u0026rsquo; to \\ten. Substitute Text with Regular Expressions Regular expressions can be used for string substitution.\nimport re def main(): s = \u0026#34;the the quick brown fox jumps over the the the lazy fox\u0026#34; regex = re.compile(r\u0026#34;(\\bthe\\s+)+\u0026#34;) processed_s = regex.sub(\u0026#34;the \u0026#34;, s) print(processed_s) if __name__ == \u0026#34;__main__\u0026#34;: main() Explanation\nregex.sub(\u0026quot;the\u0026quot;, s) means to search pattern regex in s, and replace the matching entries to \u0026quot;the\u0026quot;. () meta characters in regular expression mean a group. re.sub also accepts a function for more complex substition. Suppose we want to convert all three-letter words to uppercase.\nimport re def main(): s = \u0026#34;the quick brown fox jumps over the lazy fox\u0026#34; regex = re.compile(r\u0026#34;\\b\\w{3}\\b\u0026#34;) processed_s = regex.sub(lambda m: str.upper(m.group()), s) print(processed_s) if __name__ == \u0026#34;__main__\u0026#34;: main() Explanation\nregex.sub accepts a function, and a lambda expression is passed to convert the matching entry to uppercase. re.Match object represents the matching entry, and it contains much useful info The whole match can be retrieved from re.Match object via group(0) method or simply group() The separate groups can be retrieved from re.Match object via group(\u0026lt;num\u0026gt;). For example, to retrieve the first group, use group(1). Types The type built-in function returns an object representing the type of the object\n\u0026gt;\u0026gt;\u0026gt; type(523) \u0026lt;class \u0026#39;int\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; type(\u0026#34;Python\u0026#34;) \u0026lt;class \u0026#39;str\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; type(1) == type(2) True \u0026gt;\u0026gt;\u0026gt; type(1) == int True \u0026gt;\u0026gt;\u0026gt; type({}) == dict True The __class__ on instance and __base__ on type also return a type object.\n# 1.__class__ causes error because python expects fractional part after `1.` \u0026gt;\u0026gt;\u0026gt; (1).__class__ \u0026lt;class \u0026#39;int\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; (1).__class__.__base__ \u0026lt;class \u0026#39;object\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; type(1) == (1).__class__ True isinstance(\u0026lt;inst\u0026gt;, \u0026lt;type\u0026gt;) and issubclass(\u0026lt;sub_class\u0026gt;, \u0026lt;super_class\u0026gt;) also provide useful type info.\n\u0026gt;\u0026gt;\u0026gt; class Person: ... pass ... \u0026gt;\u0026gt;\u0026gt; class Student(Person): ... pass ... \u0026gt;\u0026gt;\u0026gt; alex = Student() \u0026gt;\u0026gt;\u0026gt; isinstance(alex, Person) True \u0026gt;\u0026gt;\u0026gt; issubclass(Student, Person) True # notice the __main__ module is part of the full qualified name \u0026gt;\u0026gt;\u0026gt; type(alex) \u0026lt;class \u0026#39;__main__.Student\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; alex.__class__.__base__ \u0026lt;class \u0026#39;__main__.Person\u0026#39;\u0026gt; \u0026gt;\u0026gt;\u0026gt; type(alex).__name__ \u0026#39;Student\u0026#39; Duck Typing If it walks like a duck and it quacks like a duck, then it must be a duck.\nThis is Python\u0026rsquo;s philosophy for determining whether an object is of the desired type — focusing on its interface rather than its declared type, as languages like Java and C# do. As a result, the ntimes function below works for any type that supports multiplication operator:\n\u0026gt;\u0026gt;\u0026gt; def ntimes(obj, times): ... return obj * times ... \u0026gt;\u0026gt;\u0026gt; ntimes(2, 3) 6 \u0026gt;\u0026gt;\u0026gt; ntimes(\u0026#34;2\u0026#34;, 3) \u0026#39;222\u0026#39; \u0026gt;\u0026gt;\u0026gt; ntimes([7, 8, 9], 3) [7, 8, 9, 7, 8, 9, 7, 8, 9] Dunder Methods for Type Behaviors In Python some methods are named with leading and trailing double underscores, for example __init__, __repr__ and __str__. These methods have special meaning to Python and are not meant to be called directly. For instance, __str__ is called by Python when it needs a human-readable string representation of an object.\nclass Color: def __init__(self, red, green, blue): self.red = red self.green = green self.blue = blue def __str__(self): return f\u0026#34;Color(r={self.red}, g={self.green}, b={self.blue})\u0026#34; \u0026gt;\u0026gt;\u0026gt; c = Color(255, 128, 23) \u0026gt;\u0026gt;\u0026gt; print(c) Color(r=255, g=128, b=23) Packages Python code can be organized in a module. However when a Python project scales, holding the entire project in a single module file is not a good design. Instead, code can be splitted into multiple module files and those module files form a package.\nA package is a directory containing modules and possibly sub-directories. The name of the package is determined by the name of the directory. The qualified name of an item in a module is \u0026lt;package_name\u0026gt;.\u0026lt;sub_dir_name\u0026gt;.\u0026lt;module_name\u0026gt;.\u0026lt;item\u0026gt;. For example, suppose there is below structure\npackage my_package sub-directory my_sdir module file my_module.py variable x defined in my_module.py The qualified name of x is my_package.my_sdir.my_module.x.\n\u0026gt;\u0026gt;\u0026gt; import my_package.my_sdir.my_module \u0026gt;\u0026gt;\u0026gt; my_package.my_sdir.my_module.x 523 Simply referring to x as x or my_module.x causes NameError.\n\u0026gt;\u0026gt;\u0026gt; x Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;x\u0026#39; is not defined \u0026gt;\u0026gt;\u0026gt; my_module.x Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;my_module\u0026#39; is not defined __init__.py in Packages A package is a generalization of a module. When a package is imported for the first time, its __init__.py file is executed. Given the following setup:\n# my_package/__init__.py print(\u0026#34;Loading my_package...\u0026#34;) # my_package/my_sdir/__init__.py print(\u0026#34;Loading my_sdir...\u0026#34;) Importing a deeply nested module triggers each __init__.py in order, from the outermost package inward:\n\u0026gt;\u0026gt;\u0026gt; import my_package.my_sdir.my_module Loading my_package... Loadin my_sdir... On subsequent imports, Python skips execution since the packages are already cached in sys.modules:\n# no output; already loaded \u0026gt;\u0026gt;\u0026gt; import my_package.my_sdir.my_module \u0026gt;\u0026gt;\u0026gt; Note:\nImports are resolved level by level, from outermost to innermost. Each __init__.py runs only once, on the first import. Building an import Chain with __init__.py You can use __init__.py to automatically import subpackages and modules, so that importing a top-level package pulls in everything beneath it:\n# `my_package/__init__.py` file contains below contents print(\u0026#34;Entering my_package, importing my_sdir...\u0026#34;) from . import my_sdir print(\u0026#34;Loaded my_sdir.\u0026#34;) # `my_package/my_sdir/__init__.py` file contains below contents print(\u0026#34;Entering my_sdir, importing my_module...\u0026#34;) from . import my_module print(\u0026#34;Loaded my_module.\u0026#34;) Now importing just my_package triggers the entire chain:\n\u0026gt;\u0026gt;\u0026gt; import my_package Entering my_package, importing my_sdir... Entering my_sdir, importing my_module... Loaded my_module. Loaded my_sdir. Because the chain imported my_sdir and my_module as attributes of their parent packages, the fully qualified name resolves correctly:\n\u0026gt;\u0026gt;\u0026gt; my_package.my_sdir.my_module.x 523 However, unqualified names are not injected into the current namespace, so these still raise a NameError:\n# my_module.x and x still cause NameError \u0026gt;\u0026gt;\u0026gt; my_module.x Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;my_module\u0026#39; is not defined \u0026gt;\u0026gt;\u0026gt; x Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;x\u0026#39; is not defined from \u0026lt;package\u0026gt; import * from \u0026lt;package/module\u0026gt; import * imports all public items from a package or module. Public items are defined by an __all__ list in __init__.py or the module file itself.\n# my_package/my_sdir/my_module.py # note items are listed as strings __all__ = [\u0026#39;x\u0026#39;] x = 523 y = 311 With __all__ set to ['x'], a wildcard import brings in only x, and y is excluded:\n\u0026gt;\u0026gt;\u0026gt; from my_package.my_sdir.my_module import * \u0026gt;\u0026gt;\u0026gt; x 523 \u0026gt;\u0026gt;\u0026gt; y Traceback (most recent call last): File \u0026#34;\u0026lt;stdin\u0026gt;\u0026#34;, line 1, in \u0026lt;module\u0026gt; NameError: name \u0026#39;y\u0026#39; is not defined Use Case of Wildcard Imports from \u0026lt;package/module\u0026gt; import * is generally considered a code smell, since it can pollute the namespace with names you didn\u0026rsquo;t explicitly request. That said, one legitimate use case is flattening a package\u0026rsquo;s public API in __init__.py. For example:\n# my_package/my_sdir/my_module.py __all__ = [\u0026#39;x\u0026#39;] x = 523 y = 311 # my_package/__init__.py from .my_sdir.my_module import * Now x is surfaced directly under the my_package namespace, even though it\u0026rsquo;s defined deep inside a submodule. Users can access it without knowing the internal structure:\n\u0026gt;\u0026gt;\u0026gt; import my_package \u0026gt;\u0026gt;\u0026gt; my_package.x 523 It\u0026rsquo;s worth noting that __all__ should also be defined in my_package/__init__.py itself. Without it, from my_package import * falls back to exporting everything that doesn\u0026rsquo;t start with an underscore — which is unpredictable. Adding it explicitly claims x as part of my_package\u0026rsquo;s public API at the facade level:\n# my_package/__init__.py __all__ = [\u0026#39;x\u0026#39;] from .my_sdir.my_module import * Now the two __all__ lists work in tandem:\n__all__ in my_module.py controls what the wildcard import pulls in. __all__ in my_package/__init__.py controls what my_package itself advertises as public. Libraries with Virtul Environment Python follows a \u0026ldquo;batteries included\u0026rdquo; philosophy, and many useful libraries are shipped with the Python interpreter in the installer. For example:\nre module: regular expression support math and match modules: for real and complex number math copy module: for shallow and deep copy operations os module: for miscellaneous OS operations pdb module: Python debugger Install Libraries However, there will be cases where a desired library is not installed by default. We can use pip to install it. For example, to use the requests package (Python HTTP for Humans):\npython -m pip install requests # or use `pip` command directly pip install requests pip searches for the module in the Python Package index(PyPI). On the web page, you can search for a desired package by name. If a specific version is needed, specifiy it in the command:\npip install requests==2.23.0 To upgrade an installed package, use the --upgrade option:\npip install --upgrade requests Virtual Environment with venv To avoid the classic version conflict issue, it is recommended to use a virtual environment. Think of a Python virtual envionrment as a self-contained directory representing an entire Python environment, including the Python interpreter and installed packages.\nThe venv module is included in the Python 3 installer, so it is available by default. The example below is based on Windows PowerShell:\n# create a virtual environment named \u0026#39;lab1-env\u0026#39; python -m venv lab1-env This command creates a \u0026rsquo;lab1-env\u0026rsquo; directory representing the virtual enviornment. To use it, you must activate it first. The activation script is located in \u0026lt;virtual_environment\u0026gt;/Scripts. Depending on your OS, the script file may differ. On Windows, there are two options:\nA Powershell activation script Activate.ps1 A Command Prompt batch file activate.bat Run the appropriate file for your shell. Once activated, the environment name appears at the beginning of your prompt:\nPS C:\\temp\\lab1-env\\Scripts\u0026gt; .\\Activate.ps1 (lab1-env) PS C:\\temp\\lab1-env\\Scripts\u0026gt; Now pip install command will install packages into the activated virtual environment:\n(lab1-env) PS C:\\temp\\lab1-env\\Scripts\u0026gt; pip install requests==2.23.0 Collecting requests==2.23.0 Downloading requests-2.23.0-py2.py3-none-any.whl (58 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 58.4/58.4 kB 77.1 kB/s eta 0:00:00 Collecting chardet\u0026lt;4,\u0026gt;=3.0.2 Downloading chardet-3.0.4-py2.py3-none-any.whl (133 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 133.4/133.4 kB 67.9 kB/s eta 0:00:00 Collecting idna\u0026lt;3,\u0026gt;=2.5 Downloading idna-2.10-py2.py3-none-any.whl (58 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 58.8/58.8 kB 66.2 kB/s eta 0:00:00 Collecting urllib3!=1.25.0,!=1.25.1,\u0026lt;1.26,\u0026gt;=1.21.1 Downloading urllib3-1.25.11-py2.py3-none-any.whl (127 kB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 128.0/128.0 kB 127.8 kB/s eta 0:00:00 Collecting certifi\u0026gt;=2017.4.17 Using cached certifi-2026.2.25-py3-none-any.whl (153 kB) Installing collected packages: chardet, urllib3, idna, certifi, requests Successfully installed certifi-2026.2.25 chardet-3.0.4 idna-2.10 requests-2.23.0 urllib3-1.25.11 The downloaded package files are stored in \u0026lt;virtual_environment\u0026gt;/Lib/site-packages directory.\n","date":"2025-10-18T21:35:08+08:00","permalink":"https://blog.cxu.my.id/post/programming-languages/python/cheatsheet/","title":"Python Quick Guide"}]