Back to Home

Python Unobvious behavior of some designs

python

Python Unobvious behavior of some designs

    Examples of such constructions are considered + some obvious, but no less dangerous constructions, which should be avoided in the code. The article is designed for python programmers with experience 0 - 1.5 years. Experienced developers can criticize or supplement their comments with comments.

    1. Lambda.
    pw variable in lambda - a reference to a variable, not a value. By the time the function is called, the variable pw is 5
    Problem code
    to_pow = {}
    for pw in xrange(5): 
        to_pow[pw] = lambda x: x ** pw 
    print to_pow[2](10)  # 10000 ??? 
    

    Solution: Pass all variables to lambda explicitly
    to_pow = {}                       
    for pw in xrange(5):
        to_pow[pw] = lambda x, pw=pw: x ** pw 
    print to_pow[2](10)  # 100 
    



    2. Excellent order of attribute search during rhomboidal inheritance in classical and new style classes

    class A():
        def field(self):
            return 'a'
    class B(A):
         pass
    class C(A):
         def field(self):
            return 'c'
    class Entity(B, C):
        pass
    print Entity().field()  # a !!!
    

    class A():
        def field(self):
            return 'a'
    class B(A):
         pass
    class C(A, object):  # New style class
         def field(self):
            return 'c'
    class Entity(B, C):
         pass
    print Entity().field()  # c !!!
    



    3. Modifiable objects as default values
    Magic:
    def get_data(val=[]):
        val.append(1)
        return val
    print get_data()  # [1]
    print get_data()  # [1, 1]    ???
    print get_data()  # [1, 1, 1]    ???
    

    Decision:
    def get_data(val=None): 
        val = val or []
        val.append(1)
        return val 
    print get_data()  # [1]  
    print get_data()  # [1]
    


    val = val or [] looks shorter and quite acceptable, but if 0 is not passed to the function input, an empty string, False, etc. Then you need to check is None, as described in gogle-style google-styleguide.googlecode.com/svn/trunk/pyguide.html?showone=Default_Argument_Values#Default_Argument_Values

    (Those who have not read this document - I advise you to definitely take a look.)

    4. Values initialized once by default
    import random
    def get_random_id(rand_id=random.randint(1, 100)):
        return rand_id
    print get_random_id()  # 53
    print get_random_id()  # 53 ??? 
    print get_random_id()  # 53 ???
    


    5. The hierarchy of exceptions is not taken into account. If you do not keep in mind such lists docs.python.org/2/library/exceptions.html#exception-hierarchy + exception lists of built-in modules + exception hierarchy of your application, and also do not use PyLint. Then you can write the following:

    KeyError will never work

    try:
        d = {}
        d['xxx']
    except LookupError:
        print '1'
    except KeyError:
        print '2'
    


    6. Interpreter caching of short strings and numbers

    str1 = 'x' * 100
    str2 = 'x' * 100
    print str1 is str2  # False
    str1 = 'x' * 10
    str2 = 'x' * 10
    print str1 is str2  # True ???
    


    7. Implicit concatenation.
    A missing comma does not throw exceptions, but merges adjacent lines. This situation can happen when after the last element in the tuple they did not put an optional comma, and then the lines in the tuple regrouped, sorted
    tpl = (
        '1_3_dfsf_sdfsf',
        '3_11_sdfd_jfg',
        '7_17_1dsd12asf sa321fs afsfffsdfs'
        '11_19_dfgdfg211123sdg sdgsdgf dsfg',
        '13_7_dsfgs dgfdsgdg',
        '24_12_dasdsfgs dgfdsgdg',
    )
    


    8. The nature of the Boolean type.
    True and False are the most real 1 and 0, for which they came up with a special name for greater expressiveness of the language.
    try:
        print True + 10 / False * 3
    except Exception as e:
        print e  # integer division or modulo by zero
    


    >>> type(True).__mro__
    (, , )
    


    docs.python.org/release/2.3.5/whatsnew/section-bool.html
    Python's Booleans were added with the primary goal of making code clearer.

    To sum up True and False in a sentence: they're alternative ways to spell the integer values ​​1 and 0, with the single difference that str () and repr () return the strings' True 'and' False 'instead of' 1 'and' 0 '.


    8*. Outdated design. It is dangerous because losing a slash or transferring only the first part of an expression does not lead to obvious errors. As a solution, use parentheses.
    x = 1 + 2 + 3 \
    + 4
    

    ps: item number 8 * due to the fact that initially in the article there were by mistake two 7th points. In order not to disrupt the numbering (since there are already links to it in comments), we had to introduce such a notation.

    9. The comparison operators and, or, unlike for example PHP, do not return True or False, but return one of the comparison elements, in this example current_lang will be assigned the first positive element
    current_lang =  from_GET or from_Session or from_DB or DEFAULT_LANG
    


    Such an expression as an alternative to the ternary operator will also work, but it is worth paying attention that if the first element of the list is ``, 0, False, None, then the last element in the comparison will be returned, but not the first element of the list.
     a = ['one', 'two', 'three']
    print a and a[0] or None  # one
    


    10. Catch all exceptions and still do not handle them. In this example, nothing unusual happens, but this design is fraught with danger and is very popular among beginners. It’s not worth writing like that, even if you are 100% sure that the exception can not be handled in any way, since this does not guarantee that another developer will not add a line to the try-except block, from which I would like to catch an exception. Solution: write to the log, specify the type of exceptions caught, frame try-except only the minimum necessary piece of code.

    try:
        # Много кода, чем больше тем хуже
    except Exception:                                                           
        pass
    


    11. Overriding objects from built-in. In this case, the list, id, type objects are redefined. Using the classic id, type in a function and the list class in a module in the usual way will fail. As a solution, install PyLint in your IDE and keep track of what it tells you.

    def list(id=DEFAULT_ID, type=TYPES.ANY_TYPE):                                                                       
        """                                                                                        
        W0622 Redefining built-in "id" [pylint]                                                    
        """                                                                                        
        return Item.objects.filter(id=id, item_type=type)  
    


    If you can’t rewrite the code in any way, then you will have to access the built-in functions like this:
    def list(id=None):
        print(__builtins__.id(list))
    


    12. The working code, without surprises ... for you ... But another developer using mod2.py will be very surprised to notice that one of the module attributes suddenly changed unexpectedly. As a solution, try to avoid such actions, or at least introduce a function in mod2.py to redefine the attribute. Then by studying mod2.py it will be possible even to understand that one of the module attributes can change.

        # mod1.py                                                                                  
        import mod2                                                                                
        mod2.any_attr = '123' 
    


    UPD: A useful link on a topic from lega : Hidden features of Python

    PS: Criticism, comments, additions are welcome.

    Read Next