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

# pyramid.py
# [or]
# pyramid_giza.py

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

pyramid_giza = "pyramid of giza" # Public
_pyramid_giza = "pyramid of giza" # Protected
__pyramid_giza = "pyramid of giza" # Private

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

def draw_pyramid_giza(): # Public
    pass
def _draw_pyramid_giza(): # Protected
    pass
def __draw_pyramid_giza(): # Private
    pass

8. Method Arguments

  • Instance methods should have their first argument named ‘self’.

  • Class methods should have their first argument named ‘cls’

class PyramidGiza(object):
        def instance_method(self):
            print(f'Hello from {self.__class__.__name__}')

        @classmethod
        def class_method(cls):
            print(f'Hello from {cls.__name__}')

        @staticmethod
        def static_method():
            print(f'Hello from {PyramidGiza.static_method.__name__}')

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

TOTAL = 100
# [or]
MAX_CAPACITY = 200

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...