Show Me The Code
Before we begin to write code
Clean code is ...?
- focused
-
Each function, class, or module should do one thing and do it well
- easy to read and speak about
-
Clean code is read like well-written prose
- easy to debug
-
The logic should be straightforward to make it hard for bugs to hide
- easy to maintain
-
That is it can easily be read and enhanced by other developers
- highly performant
-
performance close to optimal so as not to tempt people to make the code messy with unprincipled optimizations
Naming conventions
1. General
- Avoid using names that are too general or too wordy. Strike a good balance between the two. Use descriptive names that are easy to read.
Bad: data_structure, my_list, info_map, dictionary_for_the_purpose_of_storing_data_representing_word_definitions
Good: user_profile, menu_options, word_definitions
Don't be a jackass and name things "O", "l", "O1" or "I". Avoid using ambiguous shorthand.
When using CamelCase names, capitalize all letters of an abbreviation (e.g. HTTPServer)
Always use the same vocabulary
Don't use magic numbers
# Not recommended # The au variable is the number of active users au = 105 c = ["UK", "USA", "UAE"] fn = 'John' Ln = 'Doe' cre_tmstp = 1621535852 client_first_name = 'John' customer_last_name = 'Doe' from random import random def roll_dice(): return random.randint(0, 4) # what is 4 supposed to represent? # Recommended total_active_users = 105 countries = ["UK", "USA", "UAE"] first_name = 'John' Las_name = 'Doe' creation_timestamp = 1621535852 client_first_name = 'John' client_last_name = 'Doe' DICE_SIDES = 4 def roll_dice(): return random.randint(0, DICE_SIDES)
2. Packages
Package names should be all lower case
When multiple words are needed, an underscore should separate them
It is usually preferable to stick to 1 word names
3. Modules
Module names should be all lower case
When multiple words are needed, an underscore should separate them
It is usually preferable to stick to 1 word names
4. Classes
Class names should follow the UpperCaseCamelCase convention
Python's built-in classes, however are typically lowercase words
Exception classes should end with “Error”
Do not add redundant context
class PyramidGiza: pass class InputError(Exception): # custom exception pass # Not recommended class PersonBad: def __init__(self, person_username, person_email, person_phone, person_address): self.person_username = person_username self.person_email = person_email self.person_phone = person_phone self.person_address = person_address # Recommended class PersonGood: """Since we are already inside the Person class, there's no need to add the person_ prefix to every class variable.""" def __init__(self, username, email, phone, address): self.username = username self.email = email self.phone = phone self.address = address
5. Global (module-level) Variables
Global variables should be all lowercase
Words in a global variable name should be separated by an underscore
6. Instance Variables
Instance variable names should be all lower case
Words in an instance variable name should be separated by an underscore
Non-public instance variables should begin with a single underscore
If an instance name needs to be mangled, two underscores may begin its name
7. Methods
Method names should be all lower case
Words in a method name should be separated by an underscore
Non-public method should begin with a single underscore
If a method name needs to be mangled, two underscores may begin its name
8. Method Arguments
Instance methods should have their first argument named ‘self’.
Class methods should have their first argument named ‘cls’
9. Functions
Function names should be all lower case
Words in a function name should be separated by an underscore
10. Constants
Constant names must be fully capitalized
Words in a constant name should be separated by an underscore
Exploring syntax
listings/snake_walks_the_big_story.py (Source)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 |
# By adding the line #!/usr/bin/python3 on the top of the script, we can run the file.py # on a Unix system & automatically will understand that this is a python script. # !/usr/bin/python3 """A one line summary of the module or program, terminated by a period. Leave one blank line. The rest of this docstring should contain an overall description of the module or program. Optionally, it may also contain a brief description of exported classes and functions and/or usage examples. PEP = Python Enhancement Proposal Typical usage example: foo = ClassFoo() bar = foo.get_bar() """ # ####### 1. STL imports import datetime import importlib import math import os import platform import random import sys from copy import copy, deepcopy from pprint import pprint # ####### 2. 3rd-party packages from colorama import Fore, Back, Style import requests # fyi: Such import style will cause issues with built-in funcs - if names match, they will be overridden. # from os import * # ####### 3. Your own packages # import model_name from models_package GRAVITATIONAL_CONSTANT: float = 9.81 # Write a full sentence in one-line comment with a period in the end. multiline_comment = ''' Not a Module docstring \t but comment assigned to variable.\n This will be a second line if printed. ''' def explore_file_handling(): main_dir: str = os.path.split(os.path.abspath(path=__file__))[0] data_dir: str = os.path.join(main_dir, 'data') test_data_file = 'test_data.txt' test_data_path = f'{data_dir}/{test_data_file}' try: # context manager with open(file=test_data_path, mode='x') as file_obj: antigravity_trial = random.randint(a=8, b=10) if 8 < antigravity_trial <= int(GRAVITATIONAL_CONSTANT): import antigravity print(antigravity.__name__) else: module_path = importlib.import_module('exercises/python_zen') file_obj.write(str(module_path)) except FileExistsError as file_error: os.remove(test_data_path) print(f'Existed file was removed', file_error) else: print("This code will execute if no exception happened.") print("\nLet's try to overcome gravity!\n") finally: print('*' * 20) print('Ok, double-check your current directory:\n') os.system('ls -l') def explore_data_types(): """Remember LEGB rule.""" # Note: Avoid + operator for string concatenation. Prefer string formatting. data_types_question = 'data types in Python. Write corresponding classes (separated by space): ' entered_immutable_data_types = input(f'Name immutable {data_types_question}').strip().split() entered_mutable_data_types = input(f'Name mutable {data_types_question}').strip().split() # Define function with parameters def check_data_types(user_input: list = None, data=None) -> bool: """Test user knowledge of basic data types.""" # TIP: check list for emptiness if not user_input: user_input = [] # shorthand instead of if... statement data = data or [] # int float bool str bytes tuple frozenset # list dict set bytearray immutable_data_types = (int, float, bool, str, bytes, tuple, frozenset) mutable_data_types = (list, dict, set, bytearray,) builtins_data_types = immutable_data_types + mutable_data_types print('builtins_data_types:', builtins_data_types) # Create generator and convert it to tuple builtins_data_types_as_strings = tuple((dt.__name__ for dt in builtins_data_types)) print('builtins_data_types_as_strings:', builtins_data_types_as_strings) unique_entered_types = set(user_input) print('unique_entered_types:', unique_entered_types) correct_types = list(filter(lambda dt: dt in builtins_data_types_as_strings, unique_entered_types)) print('correct_types:', correct_types) # aka double-check if len(correct_types) == int('11', base=10): print(f'You named all data types!') return True elif len(correct_types) > int('11', base=10): print('Do you know more than me?') return bool(0) else: missed_types = set(builtins_data_types_as_strings) - unique_entered_types print(f'You\'ve missed {missed_types}') return False check_data_types(user_input=entered_immutable_data_types + entered_mutable_data_types) def show_basic_operators(): first_name = 'Ivan' last_name = 'Prytula' # print('{first_name} {last_name}'.format()) print('{0} {1}'.format(first_name, last_name)) # f-string format datetime now = datetime.datetime.now() print(f'{now:%Y-%m-%d %H:%M}') user_age = 33 admin_age = 35 user_salary = 1_000_000 admin_salary = 2_000_000.5 total_salary = (user_salary + admin_salary) ** 2 % 7 // 2 / 2.0 * 4000 + 100 - 4 # -> 4096.0 total_salary += 200 total_salary -= 400 total_salary *= 5 total_salary /= 3 total_salary //= 3 total_salary **= 2 total_salary %= 3 is_lost = None funny_list = ['is_lost' * 5] print(funny_list) del is_lost # print(id(is_lost)) # UnboundLocalError: local variable 'is_lost' referenced before assignment print('Final salary:', round(total_salary, 3)) # -> 6493.333 # f-string format floats print(f'Final age: {user_age / admin_age:.5f}', ) # -> 0.94286 # P E M IntDiv D M A S # () ** % // / * + - # Operators _Operation_ _Example_ # ** Exponent 2 ** 3 = 8 # % Modulus/Remainder 22 % 8 = 6 # // Integer division 22 // 8 = 2 # / Division 22 / 8 = 2.75 # * Multiplication 3 * 3 = 9 # - Subtraction 5 - 2 = 3 # + Addition 2 + 2 = 4 # f-string format width for x in range(1, 11): print(f'{x:002} {x * x:3} {x * x * x:4}') # f-string justify string s1 = 'a' s2 = 'ab' s3 = 'abc' s4 = 'abcd' print(f'{s1:>10}') print(f'{s2:>10}') print(f'{s3:>10}') print(f'{s4:>10}') # f-string numeric notations numeric_notation = numeric_notation_two = 300 # hexadecimal print(f"{numeric_notation:x}") # 12c # octal print(f"{numeric_notation:o}") # 453 # scientific print(f"{numeric_notation:e}") # 3.000000e+02 def show_comprehensions(): numbers_one = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) numbers_two = copy(numbers_one) test_str2 = "let's do more Python!".title() # Let'S Do More Python! # TIP: "".join(<iterable_of_substrings>) is 2-3 times faster than "for-loop s += subsrings" res2 = ''.join([str(ord(elem)) for elem in test_str2]) # 76101116398332681113277111114101328012111610411111033 doubled_numbers = [num * 2 for num in numbers_one] doubled_numbers_tuple = tuple(doubled_numbers) stringified_numbers_dict = {str(num): num for num in numbers_two if 0 < num < 10} stringified_numbers_dict.update(doubled_numbers_tuple=doubled_numbers_tuple) pprint(stringified_numbers_dict, indent=2, width=100, depth=2, compact=True) def explore_builtins(): """Sandbox to play with builtins.""" print(abs(-40)) # 40 will_be_eliminated_from_set = 1 seq_to_test = {1, 4, will_be_eliminated_from_set, 'abc', None, sys.copyright} print(all(seq_to_test), any(seq_to_test), sep='\n') # TIP: a_1 = sys.intern('hello') b_1 = sys.intern('hello' + 'd') print(a_1 is b_1) # True import keyword print('keyword.kwlist:', keyword.kwlist) # help() keywords = """ False break for not None class from or True continue global pass __peg_parser__ def if raise and del import return as elif in try assert else is while async except lambda with await finally nonlocal yield """ modules = ''' PIL _weakrefset hmac remove_empty_lines __future__ _xxsubinterpreters html reprlib _abc _xxtestfuzz http resource _aix_support _zoneinfo imaplib rlcompleter _ast abc imghdr runfiles _asyncio aifc imp runpy _bisect antigravity importing_with_asterisk sched _blake2 argparse importlib scopes _bootlocale args_passing inspect secrets _bootsubprocess array interpreterInfo select _bz2 ascii_transformation io selectors _codecs ast ipaddress setattr_getattr _codecs_cn asynchat iterators_vs_lists setup_cython _codecs_hk asyncio itertools setuptools _codecs_iso2022 asyncore json shelve _codecs_jp atexit json_demos shlex _codecs_kr audioop keyword shuffle_list _codecs_tw backend_interagg lib2to3 shutil _collections base64 linecache signal _collections_abc basic_collections locale site _compat_pickle bdb logging sitecustomize _compression binascii lzma smtpd _contextvars binhex mailbox smtplib _crypt bisect mailcap sndhdr _csv bs4 marshal socket _ctypes builtins math socketserver _ctypes_test bz2 mimetypes soupsieve _curses cProfile mmap spwd _curses_panel calendar modulefinder sqlite3 _datetime cgi monkey_path_module_func sqlite_demos _dbm cgitb most_frequent_word sre_compile _decimal check_palindrome multiprocessing sre_constants _distutils_hack chunk mutable_default_args sre_parse _elementtree cmath netrc ssl _functools cmd network_programming_demos stat _gdbm code nis statistics _hashlib codecs nntplib string _heapq codeop ntpath string_of_numbers _imp collections nturl2path stringprep _io colorama numbers struct _json colorsys oop_demos subprocess _locale compileall opcode sunau _lsprof concurrent operator switch_implementation _lzma configparser optparse symbol _markupbase context_managers os symtable _md5 contextlib ossaudiodev sys _multibytecodec contextvars parser sysconfig _multiprocessing copy pass_keyword syslog _opcode copying_objects pathlib tabnanny _operator copyreg pdb tarfile _osx_support count_capital_letters pickle telnetlib _peg_parser crypt pickletools tempfile _pickle csv pip termios _posixshmem ctypes pipes ternary_conditionals _posixsubprocess curses pkg_resources test _py_abc dash_m_option_shell pkgutil textwrap _pydecimal dataclasses platform this _pydev_bundle datalore plistlib threading _pydev_comm datetime polymorphism time _pydev_imps dbm poplib timeit _pydev_runfiles decimal posix tkinter _pydevd_bundle difflib posixpath tkinter_demos _pydevd_frame_eval dis pprint token _pyio distutils prep_jr_strong tokenize _queue doctest print_closer_look trace _random email print_star_triangle traceback _sha1 encapsulation profile tracemalloc _sha256 encodings pstats tty _sha3 ensurepip pty turtle _sha512 enum pwd type_hinting _shaded_ply enumerate_demo py_compile types _shaded_thriftpy errno pyclbr typing _signal exhausting_iterators pycompletionserver underscore_placeholders _sitebuiltins faulthandler pydev_app_engine_debug_startup unicodedata _socket fcntl pydev_console unittest _sqlite3 file1 pydev_coverage unpacking_sequences _sre file_for_importing pydev_ipython urllib _ssl filecmp pydev_pysrc uu _stat fileinput pydev_test_pydevd_reload uuid _statistics fnmatch pydev_tests venv _string formatter pydev_tests_mainloop warnings _strptime fractions pydev_tests_python wave _struct ftplib pydevconsole weakref _symtable functools pydevd webbrowser _sysconfigdata__linux_x86_64-linux-gnu gc pydevd_concurrency_analyser wsgiref _sysconfigdata__x86_64-linux-gnu genericpath pydevd_file_utils xdrlib _testbuffer get_pass_from_input pydevd_plugins xml _testcapi getopt pydevd_pycharm xml_demos _testimportmultiple getpass pydevd_tracing xmlrpc _testinternalcapi gettext pydoc xxlimited _testmultiphase glob pydoc_data xxsubtype _thread gorilla_lib pyexpat zip()_demo _threading_local graphlib queue zipapp _tkinter grp quopri zipfile _tracemalloc gzip random zipimport _uuid hashlib re zlib _warnings heapq readline zoneinfo _weakref help_dir regex_demos ''' symbols = """ != + <= __ " += <> ` \"\"\" , == b" % - > b' %= -= >= f" & . >> f' &= ... >>= j ' / @ r" ''' // J r' ( //= [ u" ) /= \\ u' * : ] | ** < ^ |= **= << ^= ~ *= <<= _ """ def explore_control_flow(): """The else clause in the while else statement will execute when the condition of the while loop is False and the loop runs normally without encountering the break or return statement.""" lemon = acidity = 0 money = 90 while acidity < 100: if acidity < 50: break elif lemon == 10: continue acidity = lemon + 2 if money >= 50 else lemon + 1 lemon += 1 else: print('Whoah!') def unpacking_assignment(): pairs = [(10, 5, math.pi), (8, 100, math.tau)] for left, middle, right in pairs: print(left * middle - right) # "swap variables" == collection unpacking # x, y = [35, 15] # x, y = (35, 15) # x, y = {35, 15} numbers_one = (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12) numbers_two = copy(numbers_one) print(id(numbers_one), id(numbers_two)) animals = { 'dog': { 'type': 'good', 'family': 'long_hair' } } animals_deep_copy = deepcopy(animals) print(id(animals), id(animals_deep_copy)) part_a, *part_b = numbers_one *part_c, part_d, _ = numbers_one # part_c == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # part_d == 11 # _ == rest which we don't need # print('part_c', part_c, 'part_d', part_d) # print(numbers_one[1:11:2]) def bitwise_operators(): # print(bitwise_operators.__name__) # bitwise_operators # print(bin(42)) # 0b101010 # print((42).bit_length()) # 6 # a = bin(156) # '0b10011100' # b = bin(52) # '0b110100' # (a & b) = a x b res_and = 156 & 52 res_or = 150 | 51 res_shift_left = 15 << 2 print('res_and:', res_and) # 20 == 10100 print('res_or:', res_or) # 183 == 10110111 print('res_shift_left:', res_shift_left) # 15 == 1111, added two zeros 60 == 111100 print(f"{42:032b}") # Print 42 in binary on 32 zero-padded digits print(42 == 0b101010 == 0x2a == 0o52) # True pprint(dir(dict)) def explore_f_string_with_colors(): platform_python_version = platform.python_version() if platform_python_version < '3': print(f'Am I {id(platform_python_version)} joke to you? {100_500}????') elif platform_python_version < '3.8': print(Fore.RED + '\nUpgrade you \'Python\', bro!' + Style.RESET_ALL, Back.GREEN + f'New {platform_python_version} has cool features!' + Style.RESET_ALL) else: print(f'\n\tYou already know how deep is a rabbit hole, bro... ', sys.float_info.max, sep='\b-->\b', end=' yeah!\n') def check_palindrome(): importlib.import_module('check_palindrome') def explore_scopes(): item_list = [1, 2, 3, 5] def add_item_to_list(item): item_list.append(item) add_item_to_list(8) # will work # Won't work # def add_item_to_list(item): # item_list += [item] # Won't work. need use global keyword # counter = 0 # def add_item_to_counter(): # counter += 1 # # add_item_to_counter() # TIP: Modify A List During Iteration def modify_list_during_iteration(): my_items = [1, 4, 6, 7, 9, 12, 15] for idx, el in enumerate(my_items): if not (el % 2): del my_items[idx] # [1, 4, 6, 7, 9, 12, 15] # [1, 6, 7, 9, 12, 15] # [1, 6, 7, 9, 12, 15] # [1, 6, 7, 9, 12, 15] # [1, 6, 7, 9, 15] # case 2 # my_items = [1, 4, 6, 7, 9, 12, 15] # for idx in range(len(my_items)): # if not(my_items[idx] % 2): # del my_items[idx] # # [1, 6, 7, 9, 15] # [1, 7, 9, 15] # [1, 7, 9, 15] # [1, 7, 9, 15] # IndexError: list index out of range # BETTER: is to use comprehension # my_items = [1, 4, 6, 7, 9, 12, 15] # my_items = [el for _, el in enumerate(my_items) if el % 2] # [1, 7, 9, 15] # TIP: defaultdict vs dict # history = {'stud_1': 5, 'stud_2': 4, 'stud_3': 4} # biology = {'stud_1': 3, 'stud_2': 4, 'stud_3': 4} # english = {'stud_1': 4, 'stud_2': 3, 'stud_3': 5} # # # students_points = {} # for sub in (history, biology, english): # for student, grade in sub.items(): # if student not in students_points: # students_points[student] = [] # students_points[student].append(grade) # # # students_points['stud_4'] # KeyError: 'stud_4’ # {'stud_1': [5, 3, 4], 'stud_2': [4, 4, 3], 'stud_3': [4, 4, 5]} # from collections import defaultdict # # history = {'stud_1': 5, 'stud_2': 4, 'stud_3': 4} # biology = {'stud_1': 3, 'stud_2': 4, 'stud_3': 4} # english = {'stud_1': 4, 'stud_2': 3, 'stud_3': 5} # # # students_points = defaultdict(list) # for sub in (history, biology, english): # for student, grade in sub.items(): # students_points[student].append(grade) # # # # # students_points['stud_4'] # [] # # defaultdict(<class 'list'>, {'stud_1': [5, 3, 4], 'stud_2': [4,4, 3], # 'stud_3': [4, 4, 5]}) # TIP: List vs set # lst1 = ['a', 'b', 'c', 'd'] # lst2 = ['b', 'd', 'f', 'l'] # lst3 = ['a', 'f', 'k', 'n'] # # unique_element = [] # for lst in (lst1, lst2, lst3): # for el in lst: # if el not in unique_element: # unique_element.append(el) # ['a', 'b', 'c', 'd', 'f', 'l', 'k', 'n'] # lst1 = ['a', 'b', 'c', 'd'] # lst2 = ['b', 'd', 'f', 'l'] # lst3 = ['a', 'f', 'k', 'n'] # # unique_element = set(lst1 + lst2 + lst3) # {'d', 'b', 'k', 'c', 'a', 'l', 'n', 'f'} # TIP: Tuple vs namedtuple # __problem # animals = [ # ('Vasya', 'cat', 'mammals', False, False), # ('Fred', 'frog', 'amphibians', True, False), # ('Kesha', 'parrot', 'birds', False, True), # ('Pepper', 'salmon', 'fish', True, False) # ] # # for animal in animals: # print((animal[0], animal[1], animal[2], animal[3], animal[4])) # __solution from collections import namedtuple # characteristics = [ # 'name', 'type', 'family_type', 'cold_blooded', 'can_fly’ # ] # animals = [ # ('Vasya', 'cat', 'mammals', False, False), # ('Fred', 'frog', 'amphibians', True, False), # ('Kesha', 'parrot', 'birds', False, True), # ('Pepper', 'salmon', 'fish', True, False) # ] # # # Animal = namedtuple('Animal', characteristics) # # ---------------------------------------------------- # named_animals = [Animal(*animal) for animal in animals] # # or # named_animals = [Animal._make(animal) for animal in animals] # # ---------------------------------------------------- # for animal in named_animals : # print(animal._asdict()) # TIP: Dictionary as an Alternative to If-Else # __problem # def search_tree_for_price_by_product_number(product_num): # if product_num >= 0 and product_num < 100: # price = 6575 # elif product_num >= 100 and product_num < 200: # price = 343 # elif product_num >= 200 and product_num < 300: # price = 52424 # elif product_num >= 300 and product_num < 400: # price = 8424 # elif product_num >= 400 and product_num < 500: # price = 1342 # elif product_num >= 500 and product_num < 600: # price = 2144 # elif product_num >= 600 and product_num < 700: # price = 342424 # elif product_num >= 700 and product_num < 800: # price = 554 # elif product_num >= 800 and product_num < 900: # price = 89 # elif product_num >= 900 and product_num < 1000: # price = 144424 # else: # price = 0 # return price # __solution # def search_tree_for_price_by_product_number(product_num): # keys = [el for el in range(0, 1100, 100)] # values = [6575, 343, 52424, 8424, 1342, 2144, 342424, 554, 89, 144424] # price_dict = {(keys[i], keys[i+1]): values[i] for i in range(len(keys) - 1)} # for k, v in price_dict.items(): # if product_num in range(*k): # return v # return 0 # TIP: The magic of itertools # __problem # predators = ["fox", "owl"] # preys = ["rabbit", "mouse", "squirrel"] # # combimations = [] # for predator in predators: # for prey in preys: # combimations.append((predator, prey)) # # [ # ('fox', 'rabbit'), # ('fox', 'mouse'), # ('fox', 'squirrel'), # ('owl', 'rabbit'), # ('owl', 'mouse'), # ('owl', 'squirrel') # ] # __solution # import itertools # predators = ["fox", "owl"] # preys = ["rabbit", "mouse", "squirrel"] # # combimations = list(itertools.product(predators, preys)) # # # [ # ('fox', 'rabbit'), # ('fox', 'mouse'), # ('fox', 'squirrel'), # ('owl', 'rabbit'), # ('owl', 'mouse'), # ('owl', 'squirrel') # ] # __problem2 # numbers = [1, 3, 5, 7, 4, 9, 11, 12, 13, 15, 6] # dropwhile = [] # for idx, numb in enumerate(numbers): # if not numb%2: # dropwhile = numbers[idx:] # break # [4, 9, 11, 12, 13, 15, 6] # __solution_2 # import itertools # numbers = [1, 3, 5, 7, 4, 9, 11, 12, 13, 15, 6] # dropwhile = list(itertools.dropwhile(lambda x : x%2, numbers)) # [4, 9, 11, 12, 13, 15, 6] # TIP: Pandas & numpy # __problem # rules = [ # {'dealer': "AC", 'utility': "MP", 'product': "UC12", "tax": 0.1}, # {'dealer': "AC", 'utility': "KL", 'product': "UC12", "tax": 0.15}, # {'dealer': "AC", 'utility': None, 'product': "UC12", "tax": 0.20}, # {'dealer': None, 'utility': None, 'product': "UC12", "tax": 0.25}, # ] # # filter_rule = { # "dealer": 'AC', # "utility": "not_exist", # 'product': "UC12" # } # __solution # import pandas as pd # import numpy as np # # # def filter_dataframe_by_column(rules_df, column): # # # rules_df = rules_df.loc[np.select( # [rules_df[column].isin([filter_rule[column]]).any()], # [rules_df[column] == filter_rule[column]], rules_df[column].isnull() # )] # return rules_df # # # def get_tax_from_rules(filter_rule): # # # rules_df = pd.DataFrame(rules) # for col in ('utility', 'dealer'): # rules_df = filter_dataframe_by_column(rules_df, col) # tax = rules_df['tax'].iloc[0] # return tax # # print(get_tax_from_rules(filter_rule)) # # 0.2 # TIP: Catch any error # Don’t catch any error. Always specify which exceptions you are prepared to recover from and only catch those. # Try to avoid passing in except blocks. Unless explicitly desired, this is usually not a good sign. # try: # # something # except: # pass # TIP: Test coverage # - Coverage.py is a tool for measuring code coverage of Python programs. It monitors your program, noting which parts # of the code have been executed, then analyzes the source to identify code that could have been executed but was not. # - That exact # pragma: no cover is the hint that the part of code should be ignored by the tool. if __name__ == '__main__': # Runs main() if file wasn't imported. # explore_file_handling() # explore_data_types() # show_basic_operators() # show_comprehensions() # explore_builtins() # explore_control_flow() # unpacking_assignment() # bitwise_operators.__call__() # explore_f_string_with_colors() # check_palindrome() pass |
Common Python Anti-Patterns to watch out for
listings/bad_practices.py (Source)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
""" Common Python Anti-Patterns to watch out for """ def show_bad_ugLiesTFuncEveR_patterns(abc=42, foo=[]): """ this is super cool docs for cool func. blah foo spam eggs bar buzz.......... :param abc: blah blah :param foo: blah blah :param bar: blah blah :return: I dunno what exactly, but many things """ # === Not Using Context Managers for Files === file_obj = open('abc.txt', 'r') data = file_obj.read() # noqa # do something exciting file_obj.close() # === Using type() to compare types === name = 'Alex' if type(name) == str: print('It is a string') else: print('It is not a string') # === Use of `exec()` === s = "print(\"Hello, World!\")" exec(s) # === No exception type(s) specified === try: print(5 / 0) except: print("Exception") # === Using map() or filter() instead of list comprehension === values = [1, 2, 3] doubles = map(lambda num: num * 2, values) # === Using list/dict/set comprehension unnecessarily === my_fav_superheroes = ['sdf', 'sdfdf', 'dfgdfg'] comma_seperated_names = ','.join([name for name in my_fav_superheroes]) # === Unnecessary use of generators === squares = dict((i, i ** 2) for i in range(1, 10)) # === Not using `else` where appropriate in a `for` loop === numbers = [1, 2, 3] n = 4 found = False for num in numbers: if num == n: found = True print("Number found") break if not found: print("Number not found") # === LBYL: “look before you leap” === dict_ = dict() value = None if "key" in dict_: value += dict_["key"] # === Comparing with None/Boolean values in the wrong way === number = None flag = True # ... many lines after: if number == None or flag == True: print("This works, but is not the preferred PEP 8 pattern") # === Using wildcard imports * === from collections import * # === Not using zip() to iterate over a pair of lists === numbers = [1, 2, 3] letters = ["A", "B", "C"] for index in range(len(numbers)): print(numbers[index], letters[index]) # === Using `key` in `list` to check if key is contained in list === l = [1, 2, 3, 4] if 4 in l: print("The number 4 is in the list.") else: print("The number 4 is NOT in the list.") # === Not using `get()` to return default values from a dictionary === stocks = {'AMZ': 'Amazon', 'AAPL': 'Apple'} if 'AMC' in stocks: stock_name = stocks['AMC'] else: stock_name = 'undefined' # === Not using items() to iterate over a dictionary === country_map = {'us': "USA", 'uk': "United Kingdom"} for code in country_map: name = country_map[code] # do something with name # === Not Using explicit unpacking === cars = ['BMW', 'Mazda', 'Tesla'] car_1 = cars[0] car_2 = cars[1] car_3 = cars[2] # === Not Using enumerate() in loops === cars = ['BMW', 'Mazda', 'Tesla'] for ind in range(len(cars)): print(ind, cars[ind]) # === Returning more than one object type in function call === def returning_many_types_from_function_call_bad(name, db=None): person = db.get_person(name) if person: return person.age # returns an int # returns None if person not found # === Not using literal syntax to initialize empty list/dict/tuple === my_evans = list() my_maps = dict() my_tuple = tuple() # Add something to this empty list # === Pushing debugger in production code === breakpoint() # push to prod env |
Common Python Patterns to use
listings/good_practices.py (Source)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 |
""" Common Python Patterns to use """ def show_best_practices(pos1, pos2, /, pos_or_kwd=True, *, kwd1, kwd2=""): """ Shows good and best practices for getting clean & idiomatic code. # https://deepsource.io/blog/python-positional-only-arguments/ PEP 570 :param pos1: 1st positional `only` argument :param pos2: 2nd positional `only` argument :param pos_or_kwd: after '/' -> positional `or` keyword args :param kwd1: after '*' -> keyword `only` argument :param kwd2: 2nd keyword `only` argument :return: """ # === Using `Context Managers` for Files === with open('abc.txt', 'r') as file_obj: """ Context managers in Python help to facilitate proper handling of resources, to control what to do when objects are created or destroyed. This removes the overhead of handling the creation and deletion of objects. """ data = file_obj.read() # do something exciting # === Using isinstance() to compare types === name = 'Alex' if isinstance(name, str): """isinstance() is usually the preferred way to compare types. It’s not only faster but also considers inheritance, which is often the desired behavior. In Python, you typically want to check if a given object behaves like a string or a list, not necessarily if it’s exactly a string. So instead of checking for string and all its custom subclasses, you can just use `isinstance`.""" print('It is a string') else: print('It is not a string') # === Use of `exec()` ONLY in very specific case === def print_word(): """The exec statement enables you to dynamically execute arbitrary Python code which is stored in literal strings. Building a complex string of Python code and then passing that code to exec results in code that is hard to read and hard to test. Anytime the use of exec error is encountered, you should go back to the code and check if there is a clearer, more direct way to accomplish the task.""" print("Hello, World!") print_word() # === Use exception type(s) specified === try: """Not specifying an exception type might not only hide the error but also leads to losing information about the error itself. So its better to handle the situation with the appropriate error rather than the generic exception otherwise as shown below. With this pattern, you are able to handle exceptions based on their actual exception-type. The first exception type that matches the current error is handled first. Thus, it is recommended to handle specific exception types first ( e.g., ZeroDivisionError) and generic error types (e.g., Exception) towards the end of the try-except block. """ print(5 / 0) except ZeroDivisionError as e: print(f"ZeroDivisionError: {e}") except Exception as e: print(F"Exception: {e}") else: print("No errors") finally: print('Good day') # === Using map() or filter() instead of list comprehension === """For simple transformations that can be expressed as a list comprehension, it is better to use list comprehensions over map() or filter() as map and filter are used for expressions that are too long or complicated to express with a list comprehension.""" values = [1, 2, 3] doubles = [num * 2 for num in values] # === Use generator expression directly, w/o unnecessary comprehension === """Built-in functions like `all`, `any`, `enumerate`, `iter`, `itertools.cycle`, `itertools.accumulate`, can work directly with a generator expression. They do not require comprehension. In addition to them: `all()` and `any()` in Python also support short-circuiting, but this behavior is lost if comprehension is used. This affects performance. """ my_fav_superheroes = ['sdf', 'sdfdf', 'dfgdfg'] comma_seperated_names = ','.join(name for name in my_fav_superheroes) # === Use respective comprehension instead of unnecessary use of generators === """It is unnecessary to use a generator expression within a call to list, dict or set since there are comprehensions for each of these types. Instead of using list/dict/set around a generator expression, they can be written as their respective comprehension.""" squares = {i: i ** 2 for i in range(1, 10)} # === Using `else` where appropriate in a `for` loop === """Python provides a built-in `else` clause for `for` loops. If a `for` loop completes without being prematurely interrupted by a `break` or `return` statement, then the `else` clause of the loop is executed.""" numbers = [1, 2, 3] n = 4 for num in numbers: if num == n: print("Number found") break else: print("Number not found") # === EAFP: “it’s easier to ask for forgiveness than permission”. === """The Python community uses an EAFP (easier to ask for forgiveness than permission) coding style. This coding style assumes that needed variables, files, etc. exist. Any problems are caught as exceptions. This results in a generally clean and concise style containing a lot of try and except statements.""" dict_ = {} value = None try: value += dict_["key"] print(value) except KeyError: pass # === Comparing with None/Boolean values in the Correct way === number = None flag = True # ... many lines after: if number is None or flag is True: print("PEP 8 Style Guide prefers this pattern") # === Using wildcard imports * === from collections import Counter # === Use zip() to iterate over a pair of lists === numbers = [1, 2, 3] letters = ["A", "B", "C"] for numbers_value, letters_value in zip(numbers, letters): print(numbers_value, letters_value) # === Using set/dict to check if key is in list (especially very long) === """Using key in list to iterate through a list can potentially take n iterations to complete, where n is the number of items in the list. If possible, you should change the list to a set or dictionary instead, because Python can search for items in a set or dictionary by attempting to directly accessing them without iterations, which is much more efficient (Since set in Python is implemented as a hash table and time complexity of accessing an element is O(1)). This is not applicable if your list has duplicates, and you want to find all occurrences of the element. """ original_list = [1, 2, 3, 4] s = set(original_list) if 4 in s: print("The number 4 is in the list.") else: print("The number 4 is NOT in the list.") # === Use `get()` to return default values from a dictionary === """Frequently you will see code create a variable, assign a default value to the variable, and then check a dictionary for a certain key. If the key exists, then the value of the key is copied into the value for the variable. While there is nothing wrong this, it is more concise to use the built-in method dict.get( key[, default]) from the Python Standard Library. If the key exists in the dictionary, then the value for that key is returned. If it does not exist, then the default value specified as the second argument to get() is returned.""" stocks = {'AMZ': 'Amazon', 'AAPL': 'Apple'} stock_name = stocks.get('AMC', 'undefined') # === Use items() to iterate over a dictionary === """The `items()` method on a dictionary returns an iterable with key-value tuples which can be unpacked in a `for` loop. This approach is idiomatic, and hence recommended. """ country_map = {'us': "USA", 'uk': "United Kingdom"} for code, name in country_map.items(): # do something with name pass # === Use explicit unpacking === """Unpacking is more concise and less error-prone as manually assigning multiple variables to the elements of a list is more verbose and tedious to write.""" cars = ['BMW', 'Mazda', 'Tesla'] cars2 = ['BMW', 'Mazda', 'Tesla'] cars3 = ('BMW', 'Mazda', 'Tesla', 'Audi') car_1, car_2, car_3 = cars car_1, car_2, _ = cars2 # _ -> if we don't need variable name for other values car_1, car_2, *car_3 = cars3 # car_1='BMW', car_2='Mazda', car_3=('Tesla', 'Audi') # === Use enumerate() in loops === """Creating a loop that uses an incrementing index to access each element of a list within the loop construct is not the preferred style for accessing each element in a list. The preferred style is to use enumerate() to simultaneously retrieve the index and list element.""" cars = ['BMW', 'Mazda', 'Tesla'] for index, car in enumerate(cars): print(index, car) # === Prefer returning one object type in function call === def returning_many_types_from_function_call_good(name=None, db=None): """ Having inconsistent return types in a function makes the code confusing and complex to understand, and can lead to bugs which are hard to resolve. If a function is supposed to return a given type (e.g. integer constant, list, tuple) but can something else as well, the caller of that function will always need to check for the type of value being returned. It is recommended to return only one type of object from a function. If there's a need to return something empty in some failing case, it is recommended to raise an exception that can be cleanly caught. :return: """ person = db.get_person(name) if not person: raise Exception(f'No person found with name {name}') return person.age # guaranteed to return int every time # === Use literal syntax to initialize empty list/dict/tuple === """ It is relatively slower to initialize an empty dictionary by calling dict() than using the empty literal, because the name dict must be looked up in the global scope in case it has been rebound. Same goes for the other two types — list() and tuple(). """ my_evans = [] my_maps = {} my_tuple = () my_set = set() # because there is no literal syntax for set # Add something to this empty list # === Audit code thoroughly! in production code === """ Most of us have done this at least once — while debugging code, it may happen that you push your code after you found the bug but forgotten to remove the debugger. This is critical, and can affect the behavior of the code. It is highly recommended to audit the code to remove invocations of debuggers before checking in. :return: """ # remove all breakpoint()/print()/pdb # ...some ready for dev/qa/stage/prod envs business logic... |