Python/10 Ways to Write Better Python Codes.md
... ...
@@ -0,0 +1,511 @@
1
+https://levelup.gitconnected.com/10-ways-to-write-better-python-codes-55fc862ab0ef
2
+
3
+# 10 Ways to Write Better Python Codes | by Nikita Prasad | Level Up Coding
4
+We’re almost halfway into 2024, the tech industry is moving faster than ever, due to the rise of **_Generative AI and Large Language Models_**.
5
+
6
+> **Bonus:** Read about the trending **Technology behind Generative AI!**, [**here**](https://ai.plainenglish.io/how-large-language-models-work-ae40b277ff5c).👈🏻
7
+
8
+As a data enthusiast and python developer you should be knowing these cool tips and trick to stay relevant in todays competitive market, where lakhs of tech-people were laid off.
9
+
10
+First thing to note here is that,
11
+
12
+Why Python?
13
+-----------
14
+
15
+Python is an extremely powerful general purpose programming language, highly suitable for Data Science related tasks — utilizing it’s extensive libraries and awesome frameworks like **_SciKit-Learn, TensorFlow, PyTorch_**, etc., due to it’s easy-to-understand syntax.
16
+
17
+This means having Python under your belt can provide you with flexibility to create large variety of applications, for example, **_automation tasks_** or **_building chatbots to interact with open-source LLMs_**, especially if you’re a beginner. As it’s quite straightforward and quick to learn as compared to other programming languages.
18
+
19
+So, that being said let’s get into the coding, and explore,
20
+
21
+How to write better Python codes?
22
+---------------------------------
23
+
24
+I’ll be discussing **_10 tips_** that can surely **_transform and elevate your coding practices_**.
25
+
26
+> If you find these tips helpful, do not forget to **clap** 👏🏻 and **comment**🖋️ down your thoughts!
27
+
28
+1\. Using “Generators” to Save Memory
29
+-------------------------------------
30
+
31
+Let’s say, you need to analyze a \`large\_file\` that _cannot fits into your RAM_. To tackle this problem efficiently, you can create a \`process\_large\_file\` generator function, **_to read the large file line by line_**.
32
+
33
+> Generators not only helps in processing the large data into batches, but are also **memory-efficient**, as you don’t need to store the whole data in memory.
34
+
35
+```
36
+def process_large_file(file_path):
37
+ """
38
+ Generator function to read a large file line by line.
39
+ """
40
+ with open(file_path, 'r') as file:
41
+ for line in file:
42
+ yield line
43
+
44
+# Specify the path to your large file
45
+log_file_path = 'path/to/your/large_file.txt'
46
+
47
+# Use the generator to process the large file line by line
48
+for line in process_large_file(log_file_path):
49
+ print(line.strip()) # .strip() removes any extra whitespace or newline characters
50
+```
51
+
52
+
53
+The above code displays the line by line output which is written in your \`large\_file.txt\` file.
54
+
55
+> Even **Keras Framework** uses **generator** to leverage multi-processing (to load and process data in batches) in parallel to reduce training time.
56
+
57
+2\. Using “.setdefault()” in Dictionaries
58
+-----------------------------------------
59
+
60
+Suppose, you’re _managing an inventory system_, and want to keep track of stock-levels of various products. When a new product is added to the system, _to ensure that it has default stock level if it hasn’t been set yet_.
61
+
62
+You can use `setdefault()` function to streamline this process by inserting key with a specified default value if the key is not already present in a dictionary.
63
+
64
+```
65
+# Initial inventory
66
+inventory: dict[str, int] = {"jeans": 500, "top": 600}
67
+
68
+# Add more products with default stock levels if they are not already present
69
+products_to_add: list[str] = ["skirt", "shirt", "tee"]
70
+
71
+for product in products_to_add:
72
+ inventory.setdefault(product, 500)
73
+
74
+# Print the final updated inventory
75
+print("Final updated inventory:", inventory)
76
+"""
77
+# Output:
78
+Final updated inventory: {'jeans': 500, 'top': 600, 'skirt': 500, 'shirt': 500, 'tee': 500}
79
+"""
80
+```
81
+
82
+
83
+This way you can avoid the need for **_explicit checks and assignments_**, making code more concise and readable.
84
+
85
+> **⏸️ Quick Pause:**
86
+>
87
+> Read about the breakthroughs in **Generative AI, Data Science, Data Analytics,** and much more directly into your Inbox, **Subscribe** [**HERE**](https://analyticalnikita.substack.com/)👈🏻
88
+
89
+3\. Using Dictionaries to avoid “if-elif” hell
90
+----------------------------------------------
91
+
92
+Suppose you have several functions, that you want to call depending upon the user input.
93
+
94
+Well, the most common way of solving this problem is to use `if-elif` conditions, but that approach can become very long and complex, while dealing with hundreds of functions.
95
+
96
+The alternative approach would be to **_create a dictionary_**, that contains the key — you want to check against the functions to be run as value.
97
+
98
+```
99
+from collections.abc import Callable
100
+# Function 1
101
+def first():
102
+ print("Calling First Function...")
103
+
104
+# Function 2
105
+def second():
106
+ print("Calling Second Function...")
107
+
108
+# Function 3
109
+def third():
110
+ print("Calling Third Function...")
111
+
112
+# Function Default
113
+def default():
114
+ print("Calling Default Function...")
115
+
116
+# User Input
117
+options: int = int(input("Enter an option :", ))
118
+
119
+# Dictionary to hold options as key and functions as values
120
+funcs_dict : dict[int, Callable[[], None]] = {1: first, 2: second, 3: third}
121
+
122
+# Checking if the key exist and incase it doesn't then deafult function will run
123
+final_result = funcs_dict.get(options, default)
124
+final_result()
125
+```
126
+
127
+
128
+When you run the program, you can see the following results on your screen.
129
+
130
+```
131
+"""
132
+# Output:
133
+# When Option send was 0
134
+Enter an option :0
135
+Calling Default Function...
136
+
137
+# When Option send was 1
138
+Enter an option :1
139
+Calling First Function...
140
+# and so on...
141
+"""
142
+```
143
+
144
+
145
+**_Note_**: If user asks for anything random, it’ll run the \`default()\` function.
146
+
147
+4\. Using “Counter” from Collections Module
148
+-------------------------------------------
149
+
150
+Trust me on this, while working with the large text data, _the most common task in text analysis_ includes **_identifying key terms_**, for which you’ll need to determine the **_frequency of each word in the particular document or whole corpus_** depending upon the problem statement.
151
+
152
+`Counter` provides a simple and efficient way to count elements in an iterable, abstracting the complexity of writing custom counting logic.
153
+
154
+Let’s implement this,
155
+
156
+```
157
+from collections import Counter
158
+import re
159
+
160
+# Read the text file
161
+with open("sample_text.txt", "r") as file:
162
+ text = file.read()
163
+
164
+# Clean and Tokenize the text
165
+cleaned_text: str = re.sub(r'[^\w\s]', '', text.lower().split())
166
+
167
+# Use Counter() to count the words
168
+word_counts: Counter = Counter(cleaned_text)
169
+
170
+# Printing second highest most common word
171
+most_commmon = counter.most_common(2) # passed in Number will denotes how many common numbers we want (counting starts from 1-n)
172
+print("Second Most Common Word is: ", most_commmon[0]) # print in zeroth index element from 2 most common ones
173
+
174
+"""
175
+# Output:
176
+Second Most Common Number is: ('data', 82)
177
+"""
178
+```
179
+
180
+
181
+**_Note_**: Additionally, you can also perform _arithmetic operations_, easily convert `Counter` object to other data structure like _dictionaries_, and utilize it’s useful methods like `element()`, `most_common()`, etc.
182
+
183
+5\. Using “Memoization” to Optimize Code
184
+----------------------------------------
185
+
186
+Memoization is a technique used in dynamic programming to **_improve the time complexity of recursive algorithms_**, by _reusing the expensive function calls when the same input occur again._
187
+
188
+The classic example of this is **the Rabbit Problem**, popularly known as the **Fibonacci Series**.
189
+
190
+```
191
+import time
192
+
193
+def memo_fibonacci(num: int, dictionary: dict[int, int]):
194
+ if num in dictionary:
195
+ return dictionary[num]
196
+ else:
197
+ dictionary[num] = memo_fibonacci(num-1, dictionary) + memo_fibonacci(num-2, dictionary)
198
+ return dictionary[num]
199
+
200
+# Catching using a Dictionary
201
+dictionary: dict[int, int] = {0: 1, 1: 1}
202
+
203
+# Elapsed time
204
+start_time: float = time.time()
205
+
206
+# Calling the function
207
+result: int = memo_fibonacci(48, dictionary)
208
+end_time: float = time.time()
209
+
210
+# Calculating the elapsed time
211
+elapsed_time: float = end_time - start_time
212
+print(f"Result: {result}") # Result: 7778742049
213
+print(f"Elapsed time: {elapsed_time:.6f} seconds") # Elapsed time: 0.000133 seconds
214
+```
215
+
216
+
217
+**_Note_**: Certainly, this significantly reduces the time complexity. But remember that it comes with the **_space-time tradeoff_**, as you maintain a cache to store the results, that you need to take-care off.
218
+
219
+6\. Using “@decorators” to avoid Repetitiveness
220
+-----------------------------------------------
221
+
222
+Let’s say you’re building a python project and you want to time, **_how long a function takes to run_**. Surely as above, you can use `time` functionality for that function but _what if you’ve tens or may be hundreds of functions_?
223
+
224
+It would take forever to write ‘start-time’ and ‘end-time’, _instead we can create a function \`elapsed\_time\` to do the same for us_. We’ll only have to **_add \`@elapsed\_time\`_** over the function that we want to time.
225
+
226
+> What are the \`**@decorators\`** ?
227
+>
228
+> Decorators are unique python functionality that **wrap round your existing function** to allow you to **modify or enhance functions** without changing the core logic, before or after a function executes.
229
+
230
+Python sees, the `@` symbol, and understand that this function under it needs to be passed into a function called \`elapsed\_time\`, then the function runs in \`elapsed \_time\` with the extra lines of code wrapped round it, to time any number of functions.
231
+
232
+```
233
+import time
234
+
235
+def elapsed_time(func):
236
+ def wrapper():
237
+ start_time: float = time.time()
238
+
239
+ func()
240
+
241
+ end_time: float = time.time() - start_time
242
+ print(f"{func.__name__}() took {end_time:.6f} seconds")
243
+ return wrapper
244
+
245
+@elapsed_time
246
+def some_code():
247
+ # Simulating running code..
248
+ time.sleep(0.00002)
249
+
250
+# Calling the function
251
+some_code() # some_code() took 0.000009 seconds
252
+```
253
+
254
+
255
+They’re widely used for **_logging, timing, enforcing access control_** and more.
256
+
257
+> **_Note_**: But, it is recommended to don’t over do it though, because they can also obfuscate, what you're code is actually doing.
258
+
259
+7\. Using \`dataclass\` for Clean Data Structures
260
+-------------------------------------------------
261
+
262
+It’s really tedious to write the `__init__` method repeatedly in regular classes that’s designed to hold only the data values, due to the rise of potential errors.
263
+
264
+(Image by Author)
265
+
266
+However, featured in Python 3.7, the `dataclasses` module is a more efficient way to store data that will be passed between different parts of a program.
267
+
268
+> Notice, in just few lines we can create less error prone data classes, without the need of writing a constructor and several other already implemented method manually.
269
+
270
+```
271
+from dataclasses import dataclass
272
+
273
+@dataclass
274
+class Employee:
275
+ id_: int
276
+ name: str
277
+ salary: float
278
+
279
+e1 = Employee(id_=1, name='Tusk', salary=69999.99)
280
+print(e1) # Employees(id_=1, name='Tusk', salary=69999.99)
281
+```
282
+
283
+
284
+Here, the output is also equivalent to the standard Python class implemented using `__repr__`.
285
+
286
+**_Note_:** We can also, customize the representation of our Employee class:
287
+
288
+```
289
+from dataclasses import dataclass
290
+
291
+@dataclass
292
+class Employee:
293
+ id_: int
294
+ name: str
295
+ salary: float
296
+
297
+ def __repr__(self):
298
+ return f"Employee(id_={self.id_}, name={self.name}, salary={self.salary})"
299
+
300
+ def __str__(self):
301
+ return f"{self.name} earns ${self.salary}."
302
+
303
+e1 = Employee(id_=1, name='Tusk', salary=69999.99)
304
+print(repr(e1)) # Employee(id_=1, name=Tusk, salary=69999.99)
305
+print(str(e1)) # Tusk earns $69999.99.
306
+```
307
+
308
+
309
+> **Note**:
310
+>
311
+> — The `__repr__` method provides an unambiguous representation of the `Employee` object.
312
+>
313
+> — The `__str__` method provides a more readable and concise description of the `Employee` object.
314
+
315
+Start using `dataclass` if you aren’t, to reduce the boilerplate code, making your code much more readable and maintainable.
316
+
317
+8\. Using “match” for Clean Input Handling
318
+------------------------------------------
319
+
320
+Hot off since Python 3.10, the structural pattern matching has been added as `match` patterns with associated `case` statements.
321
+
322
+Let’s say we have a class “Point”, that represents a point in the 2D coordinate system. Now, we’ll create a function “where\_is” to handle the cases that a user inputs to find the point in the 2D plane.
323
+
324
+A \`**match**\` statement takes an expression and compares its value to the successive patterns case block/s.
325
+
326
+```
327
+from dataclasses import dataclass
328
+
329
+# Defining a class using dataclass
330
+@dataclass
331
+class Point:
332
+ x: int
333
+ y: int
334
+
335
+# Match statements to handle different cases
336
+def where_is(point):
337
+ match point:
338
+ case Point(x=0, y=0):
339
+ return ("Origin")
340
+ case Point(x=0, y=y):
341
+ return (f"Y={y}")
342
+ case Point(x=x, y=0):
343
+ return (f"X={x}")
344
+ case Point(x, y):
345
+ return("Somewhere else")
346
+ # To catch anything else that the user inputs
347
+ case _:
348
+ return("Not a point")
349
+
350
+# Examples
351
+print(where_is(Point(0, 0))) # Output: Origin
352
+print(where_is(Point(0, 10))) # Output: Y=10
353
+print(where_is(Point(10, 0))) # Output: X=10
354
+print(where_is(Point(10, 10))) # Output: Somewhere else
355
+print(where_is("Not a point")) # Output: Not a point
356
+```
357
+
358
+
359
+Using `match-case` statements you can handle all the possible cases, ensuring _exhaustive pattern matching_.
360
+
361
+9(A). Using “all” Operator instead “and”
362
+----------------------------------------
363
+
364
+Imagine you’re building a user profile system, and want to validate that all required fields in a form are filled out by the user (I don’t know why would you not mark \*required in the form instead,🤷🏻‍♀️but let’s just concentrate here👇).
365
+
366
+Well, you can use `all` function that will return `True`, if and only if all elements in the provided iterable are `True`, instead of `and` conditions.
367
+
368
+```
369
+# User input from a registration form
370
+form_data: dict[str, str] = {"name" : "Nikita",
371
+ "email": "analyticalnikita@gmail.com",
372
+ "phone": "123478911"}
373
+
374
+# List of reuired fields
375
+required_fields: list[str] = ["name", "email", "phone"]
376
+
377
+# Using all operator
378
+if all(field in form_data for field in required_fields):
379
+ print("All required fields are filled out.")
380
+else:
381
+ print("Some required fields are missing or empty.")
382
+
383
+"""
384
+# Output:
385
+All required fields are filled out.
386
+"""
387
+```
388
+
389
+
390
+9(B). Using “any” Operator instead “or”
391
+---------------------------------------
392
+
393
+The `any` function returns `True`, if any element provided in the iterable is `True`.
394
+
395
+For instance, you have to restrict some permissions to certain users, based upon certain criteria for that you can use `any`, instead of `or` condition.
396
+
397
+```
398
+# List of permissions to the user
399
+user_permission: list[str] = ["read", "execute"]
400
+
401
+# Check if user has at least one of the required permissions
402
+required_permissions: list[str] = ["write", "read", "admin"]
403
+
404
+ # Using "all" operator
405
+if any(permission in user_permission for permission in required_permissions):
406
+ print(f"Since you have required permissions. Access is allowed.")
407
+else:
408
+ print("You're a standard user. Not allowed the access.")
409
+
410
+"""
411
+# Output:
412
+Since you have required permissions. Access is allowed.
413
+"""
414
+```
415
+
416
+
417
+These are some examples that shows how `any` and `all` can be used to simplify conditions that would otherwise require multiple `or` or `and` statements, respectively.
418
+
419
+Last but not the least, every programmer’s all-time _(hey, if not yours, then stick with me to learn these😉)_.
420
+
421
+10\. Using the Comprehensions for Shorter Syntax
422
+------------------------------------------------
423
+
424
+Comprehension is the powerful toolkit python provide you with for all the iterable datatypes. This gives a concise way to avoid multiple-line loops using single line, depending upon the situation.
425
+
426
+Let’s explore them one-by-one:
427
+
428
+10(A). List Comprehensions
429
+--------------------------
430
+
431
+Here, I’m using a nested-if statements example, to show you the power of list comprehension
432
+
433
+```
434
+# Nested-if using List Comprehension
435
+fruits: list[str] = ["apple", "orange", "avacado", "kiwi", "banana"]
436
+basket: list[str] = ["apple", "avacado", "apricot", "kiwi"]
437
+
438
+[i for i in fruits if i in basket if i.startswith("a")] # ['apple', 'avacado']
439
+```
440
+
441
+
442
+Similarly, you can also apply _nested-for loops_, until it make your comprehension hard-to-read.
443
+
444
+10(B). Tuple Comprehensions
445
+---------------------------
446
+
447
+As such tuple comprehensions doesn’t exist in Python. Instead you can use **generator expressions** to create tuples.
448
+
449
+```
450
+# Generator expression converted to a tuple
451
+tuple(i**2 for i in range(10))
452
+
453
+# (0, 1, 4, 9, 16, 25, 36, 49, 64, 81)
454
+```
455
+
456
+
457
+10(C). Dictionary Comprehensions
458
+--------------------------------
459
+
460
+Let say you’ve a list \`apple\_names\` and you want to print the new list that contains the length of each elements of the list \`apple\_names\`.
461
+
462
+Surely, you can use _list comprehension_ here. But do you know, you can actually use this notation to create a dictionary, it’s called, well yes, the _dictionary comprehension_.
463
+
464
+```
465
+# Creating a list of apple names
466
+apple_names: list[str] = ["apple", "pineapple", "green apple"]
467
+
468
+# Creating a dictionary with apple names as keys and their lengths as values
469
+print({apple_name: len(apple_name) for apple_name in apple_names})
470
+
471
+# {"apple": 5, "pineapple": 9, "green apple": 11}
472
+```
473
+
474
+
475
+Alternatively, it’s more readable than using loops or `dict` constructor to create a dictionary.
476
+
477
+10(D). Set Comprehensions
478
+-------------------------
479
+
480
+You can also use filtering based on certain conditions, in comprehensions.
481
+
482
+```
483
+# Creating a set with condition
484
+print({i**2 for i in range(1, 11) if i > 5})
485
+
486
+# {64, 36, 100, 49, 81}
487
+```
488
+
489
+
490
+**_Note_**: While comprehensions are expressive, this doesn’t means they’re suitable for all the cases, especially involving too complex logic.
491
+
492
+Conclusion
493
+----------
494
+
495
+Remember, **_you don’t write codes for computers but for the team you’re working with_**. So, writing better codes is really important, for others to understand your production-level codes.
496
+
497
+> Find the full scope of codes [**here**](https://github.com/nikitaprasad21/Python-Cheat-Codes-2024)**.** Star⭐ the GitHub repository.
498
+
499
+By adopting these tips, you not only **_write efficient code_** but also boost your **_productivity_**.
500
+
501
+> Additionally, to take your coding skills to next level, understand Python **Namespaces and Scopes** to better **Optimize Your Code**, [**here**](https://levelup.gitconnected.com/stop-struggling-with-namespaces-and-scopes-in-python-837c82cc8c19).👈🏻
502
+
503
+Before you go… If you have any _questions/suggestions/thoughts_, do drop me a line below. 🖋️
504
+
505
+And, if you enjoy this read **Clap _50 👏_** times and, do not forget to [**_follow_**](https://medium.com/@nikita-prasad-analyst) for future updates.
506
+
507
+> 👉 [**SUBSCRIBE**](https://analyticalnikita.substack.com/) to my newsletter, **Epochs of Data Insights,** and receive bi-weekly in-depth Data, AI & ML tips and insights_,_ right in your inbox!
508
+
509
+That’s it from me. Will talk soon! 🙋🏻‍♀️
510
+
511
+— [_Nikita Prasad_](https://medium.com/@nikita-prasad-analyst)
... ...
\ No newline at end of file