Python: The "Mutable Default" Trap Topic: Function Definitions & Memory Difficulty: Intermediate Question: What is the output of the final line of this code? def add_item(item, box=[]): box.append(item) return box print(add_item("Apple")) print(add_item("Banana")) A) ['Apple'] then ['Banana'] B) ['Apple'] then ['Apple', 'Banana'] C) ['Apple'] then Error: box is not defined D) ['Banana'] then ['Banana'] Did you know Python evaluates default arguments only once at the time of function definition? If you chose B, you understand why we usually use box=None instead!
Python Default Argument Evaluation
More Relevant Posts
-
Here's an interesting thing I came to know about Python today (which I should have known earlier, but anyway): Python can store all kinds of data types within a list or tuple is because it just stores the reference of the elements. Where as if you use something like array.array(), it will store the actual values, and hence, you will be able to store only one kind of data type in that. And as an inference, array.array() takes very less memory, since it has only the values, while a list on its whole (if you include memories of all the references it carries) takes more memory. For example, I just created a list of 10 million ints, and an array.array() of the same and ran a test, with Python 3.14 interpreter, and here's the result: Size of array.array for 10 million ints: 39.07MB Size of list for 10 million ints: 390.14MB Further more: Sequences like lists, tuples are called "container sequence" as they contain multiple objects. Sequences like array.array() are called "flat sequence" as they contain only values. The below image is taken from "Fluent Python" book, written by Luciano Ramalho, and it explains diagrammatically about Container sequence and Flat sequence. #python
To view or add a comment, sign in
-
-
I recently discovered assert_never in Python's typing module and it's one of those small things worth knowing about. Say you add a new status to your workflow, like "𝙘𝙖𝙣𝙘𝙚𝙡𝙡𝙚𝙙", and forget to handle it somewhere in the code. Nothing warns you. It runs fine until that status hits in production. The common fix is a raise 𝚅̲𝚊̲𝚕̲𝚞̲𝚎̲𝙴̲𝚛̲𝚛̲𝚘̲𝚛̲("𝚄̲𝚗̲𝚎̲𝚡̲𝚙̲𝚎̲𝚌̲𝚝̲𝚎̲𝚍̲ ̲𝚟̲𝚊̲𝚕̲𝚞̲𝚎̲") at the end. It works at runtime, but the type checker stays silent until something actually breaks. 𝗮𝘀𝘀𝗲𝗿𝘁_𝗻𝗲𝘃𝗲𝗿 does the same thing at runtime, and mypy and pyright will flag the branch statically, right in your IDE, before you ever run the code. Available in 𝘁𝘆𝗽𝗶𝗻𝗴 from Python 3.11+, or via 𝘁𝘆𝗽𝗶𝗻𝗴_𝗲𝘅𝘁𝗲𝗻𝘀𝗶𝗼𝗻𝘀 for older versions.
To view or add a comment, sign in
-
-
One concept that often confuses developers (including me at first) is reference behavior in Python. In Python, variables don’t actually store values. They store references to objects in memory. a = [1, 2] b = a Here, a and b are not two separate lists. They both point to the same list in memory. So when you do b[0] = 3 you are modifying the same objects. That means print(a) => [3,2] print(b) => [3,2] 𝗪𝗵𝘆 𝘁𝗵𝗶𝘀 𝗺𝗮𝘁𝘁𝗲𝗿𝘀: Understanding references helps avoid unexpected bugs, especially when working with: • Lists • Dictionaries • Objects • Nested data structures 𝗖𝗼𝗺𝗺𝗼𝗻 𝗠𝗶𝘀𝘁𝗮𝗸𝗲: a = [[0, 0, 0]] * 2 This doesn't create two separate list but creates two references to the same list. so when you do a[0][0] = 3 you may expect the output be a = [[3,0,0],[0,0,0]] But the actual output will be [[3,0,0],[3,0,0]] #Python #ProgrammingConcepts #SoftwareEngineering #CodingTips #LearnPython #DeveloperJourney #TechLearning
To view or add a comment, sign in
-
🚀 Python Pro-Tip: Stop using + to join strings. 🛑 If you’re building long strings or SQL queries with +, you’re hurting performance and readability. 📉 The Shortcut: f-Strings (Interpolation) ⚡ It’s faster, cleaner, and allows you to run code right inside the string. ❌ The Messy Way: url = "https://" + domain + "/" + path + "?id=" + str(user_id) ✅ The Clean Way: url = f"https://{domain}/{path}?id={user_id}" Why?? * Readability: It looks like the final result. * Performance: Python optimizes f-strings at runtime better than concatenation. * Power: You can even do math: f"Total: {price * 1.15:.2f}" Are you still using .format() or are you 100% on the f-string train? 👇 #Python #CleanCode #ProgrammingTips #SoftwareEngineering #BackendDev
To view or add a comment, sign in
-
-
If you work with Python, have you ever wondered: • What are magic methods? • Are they the same as special methods? • What about “dunder methods”? First thing is, magic methods are not really magic.They are just special methods defined by the Python Data Model. And, guess what, magic methods, special methods and dunder methods are all the same thing. Amazing, right? Let’s look at a simple example: 𝗰𝗹𝗮𝘀𝘀 𝗠𝘆𝗖𝗼𝗹𝗹𝗲𝗰𝘁𝗶𝗼𝗻: 𝗱𝗲𝗳 __𝗹𝗲𝗻__(𝘀𝗲𝗹𝗳): 𝗿𝗲𝘁𝘂𝗿𝗻 𝟰𝟮 Now, when you call: 𝗹𝗲𝗻(𝗼𝗯𝗷) You’re NOT calling your method directly, Python is. This is the key insight and it is very important as we are going to see on another post. Takeaway: “Magic methods” are not magic. They are contracts with the Python interpreter. Implementing such methods are going to open a lot of new doors and it is a very pythonic whay of implementing your code. #python #magicmethods #dundermethods #specialmethods
To view or add a comment, sign in
-
One of Python's most famous gotchas, that i came across today. Python evaluates default arguments ONCE at function definition time, so if default argument is mutable for example `def test_mutable_arg(counter={}): ` then for each function call it's the same dict object shared across all calls. Contrast this with an immutable default e.g `def test_immutable_arg(counter=0):` Here, counter rebinds the name to a new int each time because ints are immutable, any operation can't modify the original, so a fresh object is created in memory each time. The fix, use None as a sentinel: ``` def safe_function(counter=None): if counter is None: counter = {} ``` Simple rule: never use [], {}, or set() as a default argument.
To view or add a comment, sign in
-
-
Can You Spot the Problem in This Python Code? ☺️ At first glance, this code looks completely fine: def add_item(item, my_list=[]): my_list.append(item) return my_list print(add_item(1)) print(add_item(2)) print(add_item(3)) What do you expect the output to be? [1] [2] [3] But the actual output is: [1] [1, 2] [1, 2, 3] This is a classic Python pitfall: 😉 Default mutable arguments my_list=[] is created once at function definition time The same list is reused across function calls Each call keeps modifying the same object 😉 Why this matters 📌 This kind of bug: does NOT throw errors is hard to detect can silently affect logic So Correct approach 🔑 def add_item(item, my_list=None): if my_list is None: my_list = [] my_list.append(item) return my_list Never use mutable objects as default arguments in Python.
To view or add a comment, sign in
-
One of the most underrated features in Python is the dunder (double underscore) method system. These methods are what make Python objects behave like built‑ins. Examples: • __init__ → object initialization • __str__ → human readable representation • __len__ → behavior for len() • __add__ → custom + operator Example: class Vector: def __add__(self, other): return Vector(self.x + other.x, self.y + other.y) Now Python understands how to use + with your objects. Under the hood, Python transforms: a + b into: a.__add__(b) This is one reason Python feels so expressive and flexible. Understanding dunder methods means understanding how Python really works.
To view or add a comment, sign in
-
-
Python: Do you want to re-use your code? Don't forget the __name__ trick. How does it work? When a script file is called directly by the interpreter, the __name__ variable is automatically set to "__main__". If the script were imported by another script, the __name__ variable gets temporarily set to the module/called-script name while the script is running. This way, you can run the root function (defined below as "main_function") when calling the script directly, but can skip that part and import the supporting functionality if the script is imported by another script. Code re-use! Python code: def main_function(): """ This is a triple-quoted comment. Everything in between the two sets of triple-quotes is a comment. Even if there are multiple lines. Comments are ignored by the interpreter, for use by the programmer. """ pass # null command; do nothing and continue onto the next line # Do everything and call any other needed functions here. return False # Do something. In this case, return boolean False if __name__ == '__main__': main_function()
To view or add a comment, sign in
More from this author
Explore content categories
- Career
- Productivity
- Finance
- Soft Skills & Emotional Intelligence
- Project Management
- Education
- Technology
- Leadership
- Ecommerce
- User Experience
- Recruitment & HR
- Customer Experience
- Real Estate
- Marketing
- Sales
- Retail & Merchandising
- Science
- Supply Chain Management
- Future Of Work
- Consulting
- Writing
- Economics
- Artificial Intelligence
- Employee Experience
- Workplace Trends
- Fundraising
- Networking
- Corporate Social Responsibility
- Negotiation
- Communication
- Engineering
- Hospitality & Tourism
- Business Strategy
- Change Management
- Organizational Culture
- Design
- Innovation
- Event Planning
- Training & Development