Python Data Types, Variables, & Scope (PDF)

Summary

This document explains immutable and mutable data types in Python. It provides examples of different data types like lists, strings and tuples, illustrating the different behaviour of these data types when their values are modified. It also describes how Python manages dictionary keys using their hash values.

Full Transcript

Tags 1. Основи Python (Data Types, Variables, Scope) Immutable vs Mutable Immutable Objects Immutable objects cannot be changed in place after they are created. Any operation that appears to modify them actually creates a new object instead....

Tags 1. Основи Python (Data Types, Variables, Scope) Immutable vs Mutable Immutable Objects Immutable objects cannot be changed in place after they are created. Any operation that appears to modify them actually creates a new object instead. Examples include: Numbers ( int , float , complex ) Strings ( str ) Tuples ( tuple ) Frozen sets ( frozenset ) Example of Immutability: x = "hello" x = x + " world" # This creates a new string, not modifying 'x' in place print(x) # Output: 'hello world' Since strings are immutable, the concatenation ( + ) does not modify the original string but creates a new one. Mutable Objects Mutable objects can be changed in place after they are created, meaning their content can be modified without creating a new object. Examples include: Lists ( list ) Dictionaries ( dict ) Sets ( set ) Untitled 1 User-defined objects (if attributes are modifiable) Example of Mutability: lst = [1, 2, 3] lst.append(4) # Modifies the list in place print(lst) # Output: [1, 2, 3, 4] Here, the append method modifies the existing list instead of creating a new one. Key Differences Mutable Objects (e.g., Immutable Objects (e.g., Feature List) String) Can be changed in place? ✅ Yes ❌ No Uses new memory on modification? ❌ No ✅ Yes Examples list , dict , set int , str , tuple Implications of Mutability 1. Performance Considerations: Since immutable objects create a new copy when modified, frequent changes can be inefficient. 2. Safe in Multi-threading: Immutable objects prevent accidental modifications, making them safer in concurrent programming. 3. Dictionary Keys & Set Elements: Only immutable objects can be used as dictionary keys or set elements, ensuring that their value does not change unexpectedly. Example: d = { (1, 2): "valid" } # Tuples are immutable, so they can ❌ TypeError: unhashable type: 'list' be keys s = { [1, 2] } # A list cannot be a key because it is mutable. Untitled 2 Складніші типи (наприклад, списки, словники, множини) Typing module (статична типізація) Dict key type (хешування та методи `__hash__` і `__eq__`) Dictionary Key Type Rules Only immutable objects can be dictionary keys (e.g., int , str , tuple , frozenset ). Mutable objects (e.g., list , dict , set ) cannot be used as keys because their content can change, making them unreliable for key lookups. ✅ Valid Keys: d = { 1: "integer key", "name": "string key", (2, 3): "tuple key", frozenset([4, 5]): "frozenset key" } ❌ Invalid Keys (Will Raise TypeError ): ❌ TypeError: unhashable type: 'li d = { [1, 2]: "list key", # # ❌ TypeError: unhashable type: 'di st' {3: 4}: "dict key" ct' } The Role of __hash__ and __eq__ In Python, dictionary keys are stored in a hash table, which requires: 1. A hash value ( __hash__ ) to determine the key's location. 2. An equality check ( __eq__ ) to resolve key collisions. Untitled 3 __hash__ Method Returns a unique integer representing an object’s hash value. Required for objects to be used as dictionary keys or set elements. ✅ Works (Immutable) # ✅ Works (Immutable) print(hash(42)) # print(hash((1, 2, 3))) # ✅ Works (Immutable) print(hash("hello")) print(hash([1, 2, 3])) # ❌ TypeError: unhashable type: 'lis t' (Mutable) __eq__ Method Defines equality between objects. Used when different objects have the same hash value (hash collision). class CustomKey: def __init__(self, value): self.value = value def __hash__(self): return hash(self.value) # Custom hash function def __eq__(self, other): return isinstance(other, CustomKey) and self.value == other.value # Dictionary using custom objects as keys d = {CustomKey(10): "value1", CustomKey(20): "value2"} print(d[CustomKey(10)]) # Output: "value1" (since __eq__ con firms equality) Why Must Keys Be Immutable? 1. Hash Stability: Untitled 4 If an object is modified, its hash value would change, making it impossible to find in a dictionary. 2. Efficient Lookup: Hash tables rely on consistent key hashing for fast access. Example of the Problem with Mutability: ❌ TypeError: unhashable type: mutable_key = [1, 2, 3] d = {mutable_key: "value"} # 'list' # If allowed, this would break dictionary lookups: mutable_key.append(4) # Hash value would change! Key Takeaways Feature Hashable (✅) Not Hashable (❌) int , float , str ✅ tuple (with only immutable items) ✅ frozenset ✅ list , set , dict ❌ Cannot be dictionary keys LEGB (Local, Enclosed, Global, Built-in) Local (inside a function) Enclosing (inside an outer function, used in nested functions) Global (top-level module or script) Built-in (predefined Python functions and modules) Python searches for a variable in this order, stopping as soon as it finds a match. 1. Local Scope (L) Variables defined inside a function are local and only accessible within that function. Untitled 5 def func(): x = 10 # Local variable print(x) # Output: 10 ❌ func() print(x) # NameError: name 'x' is not defined (only exist s inside func) 2. Enclosing Scope (E) When a function is nested inside another function, it first looks for variables in its own local scope. If not found, it searches the enclosing function’s scope. def outer(): x = "enclosing" def inner(): print(x) # Found in enclosing scope inner() outer() # Output: "enclosing" If x were also defined inside inner(), it would use the local version instead. 3. Global Scope (G) Variables defined at the top level of a script or module are global. They can be accessed from anywhere unless shadowed by a local variable. x = "global" def func(): print(x) # Uses global variable Untitled 6 func() # Output: "global" However, if you try to modify a global variable inside a function without declaring it as global, Python will treat it as a local variable, leading to an error. x = 5 ❌ UnboundLocalError: local variable 'x' ref def change(): x = x + 1 # erenced before assignment print(x) change() To modify a global variable inside a function, use global : x = 5 def change(): global x x = x + 1 # Now allowed print(x) change() # Output: 6 4. Built-in Scope (B) Python has built-in functions like print() , len() , and open() , which are always available unless overridden. print(len("hello")) # Output: 5 (len() from built-in scope) Untitled 7 If you define a variable with the same name as a built-in function, you shadow it: ❌ TypeError: 'int' object is not call len = 42 print(len("hello")) # able To avoid this, use del : del len # Removes the redefined variable print(len("hello")) # Works again LEGB Lookup Order in Action x = "global" # Global def outer(): x = "enclosing" # Enclosing def inner(): x = "local" # Local print(x) # LEGB: Finds 'x' in Local scope first inner() outer() # Output: "local" If x were not defined inside inner() , it would use Enclosing → Global → Built-in in that order. Key Takeaways 1. Python searches for variables in the order: Local → Enclosing → Global → Built-in. 2. Modifying global variables inside functions requires global var_name. 3. Built-in functions can be shadowed if redefined, leading to errors. Untitled 8 4. Enclosing scope applies in nested functions but not between different functions. Global та Enclosed scopes 1. Global Scope (Глобальна область видимості) Глобальні змінні — це змінні, оголошені на верхньому рівні модуля або скрипта. Вони доступні з будь-якої функції, якщо там немає локального оголошення з такою ж назвою. 🔹 Читання глобальної змінної x = "глобальна" def func(): print(x) # Python знайде змінну у глобальній області func() # Виведе: "глобальна" 🔹 Спроба змінити глобальну змінну без global x = 10 ❌ def change(): x = x + 1 # UnboundLocalError: local variable 'x' ref erenced before assignment print(x) change() Помилка виникає тому, що Python вважає x локальною змінною (оскільки ми намагаємося її змінити), але ще не ініціалізував її. 🔹 Використання global для зміни глобальної змінної x = 10 Untitled 9 def change(): global x # Дозволяє змінювати глобальну змінну x = x + 1 print(x) change() # Виведе: 11 ❗ Використання — погана практика в масштабних програмах, global оскільки це ускладнює налагодження коду. 2. Enclosed Scope (Оточуюча область видимості) Оточуюча область з’являється в вкладених функціях (closure). Якщо Python не знаходить змінну у Local (L), він шукає її у функції, яка містить поточну. 🔹 Приклад доступу до змінної в Enclosed scope def outer(): x = "оточуюча" # Змінна в Enclosed scope def inner(): print(x) # Python знайде 'x' в оточуючій області inner() outer() # Виведе: "оточуюча" Python не знайшов x у Local, тому шукає в Enclosed. 🔹 Чому global не працює для Enclosed змінних? def outer(): x = "оточуюча" ❌ def inner(): global x # NameError: name 'x' is not defined x = "нове значення" Untitled 10 inner() outer() Це викличе помилку, оскільки global шукає x у глобальному просторі, а не в оточуючій функції. 🔹 Використання nonlocal для зміни змінної в Enclosed scope Щоб змінити оточуючу змінну всередині вкладеної функції, потрібно використати nonlocal : def outer(): x = "оточуюча" def inner(): nonlocal x # Дозволяє змінити змінну з Enclosed scop e x = "нове значення" inner() print(x) # Виведе: "нове значення" outer() ✅ nonlocal змінює x , яка знаходиться в найближчій оточуючій області, а не створює локальну змінну. 🔹 Висновки Scope Де знаходиться змінна? Чи можна змінити? Global На рівні модуля (поза функціями) Так, але потрібно global Enclosed У вкладених функціях (closure) Так, але потрібно nonlocal Коли що використовувати? global – якщо треба змінити змінну на рівні модуля. Untitled 11 nonlocal – якщо треба змінити змінну у вкладеній функції. Untitled 12

Use Quizgecko on...
Browser
Browser