编写高质量代码改善python的91个建议-个人收集-截至第四章

版权声明:版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/abc666666_6/article/details/86486187

目录

引论

回到目录

常量收集

包内构建一个`constant.py`文件,专门存储包内变量

目录

constant.py

class _const:
    class ConstError(TypeError):
        pass

    class ConstCaseError(ConstError): 
        pass

    def __setattr__(self, name, value):
        # 确保没有该键
        if name in self.__dict__.keys():
            raise self.ConstError("Can not change const.{name}".format(name=name))

        # 其他需求,比如要求值全小写
        if not name.isupper():
            raise self.ConstCaseError("const name '{name}' is not all uppercase".format(name=name))
        # 赋值
        self.__dict__[name] = value


import sys
sys.modules[__name__] = _const()
"""
使用sys.modules[name]可以获取一个模块对象,并可以通过该对象获取模块的属性,
这儿使用了sys.modules向系统字典中注入了一个_const对象
从而实现了在执行import本py文件时实际获取了一个_const实例的功能
"""
from moduleA import constant
# 设置常量

constant.MY_CONSTANT = 1

main.py

import sys
print(dir())
print(sys.modules.items())

from moduleA import constant

print(constant.MY_CONSTANT) # 可以打印1


if "moduleA.constant" in sys.modules.keys():
    print(dir(constant))
    print(sys.modules.items())  # 多了moduleA, moduleA.constant

回到目录


编程惯用法

回到目录

使用assert

x = 1
y = 2

assert x == y, "no equals"

# 等同于
if __debug__ and not x == y:
    raise AssertionError("no equals")
  • 使用:
    • 在函数调用后,需要确认返回值是否合理可以使用
    • 当条件是业务逻辑继续下去的先决条件时可以使用断言

回到目录

数据交换不推荐用中间变量

  • python表达式的计算顺序是从左到右
    但是表达式赋值时,表达式右边的操作数先与左边的操作数计算
    e1, e2 = e3, e4的计算顺序是e3, e4 -> e1, e2
  • 顺序如下:
    1. 计算表达式右边y,x ,先在内存中创建元组(y, x),其标识符和值分别为y,x和对应的值
      其中y,x是在初始化时已经存在于内存中的对象.
    2. 计算表达式左边的值并赋值,元组被依次分配给左边的标识符
      通过unpacking,元组第一标识符(y)分配给左边第一个元素(x); 元组第二标识符(x)分配给左边第第二个元素(y)
import dis


def swap1():
    x = 2
    y = 1
    x, y = y, x


def swap2():
    x = 2
    y = 1
    temp = x
    x = y
    y = temp


dis.dis(swap1)
print("*"*60)
dis.dis(swap2)

输出

16           0 LOAD_CONST               1 (2)
              2 STORE_FAST               0 (x)

 17           4 LOAD_CONST               2 (1)
              6 STORE_FAST               1 (y)

 18           8 LOAD_FAST                1 (y)
             10 LOAD_FAST                0 (x)
             12 ROT_TWO
             14 STORE_FAST               0 (x)
             16 STORE_FAST               1 (y)
             18 LOAD_CONST               0 (None)
             20 RETURN_VALUE
************************************************************
 22           0 LOAD_CONST               1 (2)
              2 STORE_FAST               0 (x)

 23           4 LOAD_CONST               2 (1)
              6 STORE_FAST               1 (y)

 24           8 LOAD_FAST                0 (x)
             10 STORE_FAST               2 (temp)

 25          12 LOAD_FAST                1 (y)
             14 STORE_FAST               0 (x)

 26          16 LOAD_FAST                2 (temp)
             18 STORE_FAST               1 (y)
             20 LOAD_CONST               0 (None)
             22 RETURN_VALUE

  • ROT_TWO的主要作用是置换两个栈的栈顶元素

回到目录

充分利用lazy_evaluation的特性

  • if x and y: 当x为false时,y不计算;
    if x or y: 当x为true时, y不计算。

  • 对于and条件表达式应该将值为true可能性较高的放在and后面
    对于or条件表达式应该将值为true可能性较高的变量写在or前面

  • 另一个用途是生成器

def fib():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a+b

from itertools import islice
print(list(islice(fib(), 5)))

回到目录

少用eval()

如题。

回到目录

使用enumerate()获取序列迭代的索引和值

仅当列表使用

l = [i for i in range(5)]

for i, e in enumerate(l):
    print("index is {index}, element is {element}".format(index=i, element=e))

print("*"*60)

类似

def enumerate_1(sequence, start=0):
    n = start
    for elem in sequence:
        yield n, elem
        n += 1


for i, e in enumerate_1(l):
    print("index is {index}, element is {element}".format(index=i, element=e))

回到目录

==和is

  • is 表示的是对象标识符(object identity)
    == 表示的是相同(equal)

  • is的作用是用来检查对象的标识符是否一致,也就是比较两个对象在内存中是否拥有同一块内存空间。
    ==是用来检验两个对象的值是否相等,实际调用了内部__eq__()方法。a==b相当于a.__eq__(b)

  • 所以==可以重载

  • 一般isTrue==True(NaN除外)

    a = float("NaN")
    print(a is a)
    print(a == a)
    
  • python中的string interning(字符串驻留)机制:

    • 对于小的字符串,为了提高系统性能会保留其值的一个副本,当创建新的字符串的时候直接指向该副本即可。
    • 对于长字符串并不会驻留。

回到目录


基础语法

回到目录

节制的使用from_import

结构
19-1

  • moduleA.py

    a = 1
    
  • 对于用户定义的模块,import机制会创一个新的module将其加入当前的局部命名空间中,sys.modules也会加入该模块的相关信息。但本质上引用同一个对象

  • .pyc文件为解释器生成的模块相对应的字节码

  • from a import B 将B暴露于当前局部空间,将a加载到sys.modules集合。

  • 当加载一个模块时,解释器完成以下动作

    1. 在sys.modules中进行搜索看看该模块是否已经存在,如果存在,则将其导入到当前局部命名空间,加载结束
    2. 如果在sys.modules中找不到对应模块的名称,则为需要导入的模块创建一个字段对象,并将该对象信息插入sys.modules中
    3. 加载前确认是否需要对模块对应的文件进行编译,如果需要则先进行编译。
    4. 执行动态加载,在当前模块的命名空间中执行编译后的字节码,并将其中所有的对象放入模块对应的字典中。
  • main.py

import sys
# from package.moduleA import a
# print(a)
#
# print(dir())  # 多了a
# print(sys.modules.items())  # 多了package package.moduleA

# from package import moduleA
# print(moduleA.a)
#
# print(dir())  # 多了a
# print(sys.modules.items())  # 多了package package.moduleA

import package.moduleA
print(package.moduleA.a)

print(dir())  # 多了package
print(sys.modules.items())  # 多了package package.moduleA

回到目录

i+=1不等于i++

会将++i解释成+(+1)
同理将--i解释为-(-i)
回到目录

使用with自动关闭资源

  • 实现了__enter__(self)和__exit__(self, exception_type, exception_value, traceback),即实现了上下文协议
class MyContextManager:
    def __enter__(self):
        print("entering")

    def __exit__(self, exception_type, exception_value, traceback):
        print("leaving")
        if exception_type is None:
            print("no exception")
            return False

        elif exception_type is ValueError:
            print("value error!")
            return True

        else:
            print("other error!")
            return True
  • 测试一
with MyContextManager():
    print("testing one")
"""
entering
testing one
leaving
no exception
"""
  • 测试二
with MyContextManager():
    print("testing two")
    raise ValueError
entering
testing two
leaving
value error! 

回到目录

finally中的陷阱

def finally_test():
    print("starting")

    while True:
        try:
            print("trying")
            raise TypeError("随便抛一个异常")
        except NameError as e:
            print("NameError happended {}".format(e))
            break
        finally:
            print("finally")
            break  # 这里中断了


finally_test()

打印

starting
trying
finally

try中发生了异常时,如果except中没有对应的异常,则该异常会被临时保存
finally执行完毕时,临时保存的异常会被再次抛出
但是
如果在finally中发生了异常或者break或者return,则该临时保存的异常会丢失,导致异常屏蔽

def return_test(value):
    try:
        if value == 1:
            raise ValueError("居然是1!")
        else:
            return value

    except ValueError as e:
        print(e)

    finally:
        print("END!")
        return -10086


print(return_test(1))
print(return_test(100))
居然是1!
END!
-10086

后面的-10086是抛出ValueError后进入finally,然后return -10086

END!
-10086

else要返回100,但是在返回100前会先执行finally中的语句,但是此时finally中返回了-10086,就直接返回了,而抛弃了100

所以并不推荐在finally中使用return语句进行返回

回到目录

使用join和format

如题

回到目录

可变对象和不可变对象

class Student:
    def __init__(self, name, course=[]):  # 注意这个[]
        self.__name = name
        self.__course = course


    def add_course(self, course_name):
        self.__course.append(course_name)

    def print_course(self):
        print(self.__course)


jack = Student("jack")
jack.add_course("English")
jack.add_course("Chinese")
jack.print_course()
"""
['English', 'Chinese']
"""

lee = Student("lee")
lee.add_course("Math")
lee.print_course()
"""
['English', 'Chinese', 'Math']
"""

由于__init__(self, name, course=[])course是默认参数,且默认参数在被调用的时候仅仅被评估一次。
因此实际上对象空间里面course所指向的是list的地址,每次操作的实际上是list所指向的具体列表。
这是将可变对象作为参数默认参数的时候要特别警惕的事情,对可变对象的更改会直接影响原对象。
对该问题的解决是,传入None作为默认参数。比如:

def __init__(self, name, course=None):
    self.__name = name
    if course is None:
        course = []
    self.__course = course

对于不可变对象

a = 1
print(id(a))
print(id(1))

a += 2
print(id(a))
print(id(3))

"""
1483696896
1483696896
1483696960
1483696960
"""

a中存放的是数值在内存中的地址。数值本身才是不可变对象。
a的操作只是改变a所指向的对象的地址
a+=2重新分配另一块内存地址存放结果,并将a的引用改为该内存地址。

字符串不会改变,所以多个对象同时指向一个字符串对象的时候,对其中一个对象的操作不会影响另一个对象。
回到目录

xx解析式

列表解析式不建议使用在大数据上,容易MemoryError

# 多重嵌套
alist = [["one", "two"], ["three", "four"]]
alist = [[one.upper() for one in two]for two in alist]
print(alist)

# 支持多重迭代

# 表达式可以是简单,复杂,也可以是函数
def func(num):
    return num % 2


alist = [func(num) for num in range(20)]
print(alist)


def foo(a):
    """
    参数接收一个可迭代对象时,调用时可以使用元组的简写形式
    """
    for i in a:
        print(i)


foo((i for i in range(3)))

回到目录

默认参数

def在python中是一个可执行的语句。
当解释器执行def时,默认参数也会被计算,存在与 函数.__defaults__属性中
由于python中函数参数传递的是对象,可变对象在调用者和被调用者之间共享。
函数被反复调用时,默认参数不会重新计算

def appendtest(newitem, lista=[]):  # 默认参数在函数的__defaults__中
    lista.append(newitem)
    return lista


appendtest(1)  # [1]
alist = appendtest(2)  # [1,2]
print(alist)

print(appendtest.__defaults__)  # [[1,2]]
appendtest.__defaults__[0][:] = []
print(appendtest.__defaults__)  # [[], ]

appendtest(1)  # [1]
alist = appendtest(2)  # [1, 2]
print(alist)

实例2

import time

def report(when = time.time()):
    print(when)
    pass


def report2(when = time.time):
    print(when())
    pass

# 以下内容相同
report()
print(report.__defaults__)
time.sleep(1)
report()
print(report.__defaults__)


# 动态生成,所以内容不一致
report2()
print(report2.__defaults__)
time.sleep(1)
report2()
print(report2.__defaults__)

回到目录

慎重的使用变长参数

*args *kwargs
建议下列情况使用(不仅限于下列情况)
1. 添加装饰器
2. 参数数目不确定
3. 实现函数的多台或者继承情况下子类需要调用父类的某些方法时
super(subClass, self).somefunc(*args, **kwargs)
回到目录

str()和repr()

  1. str()面对用户,目的是可读性,返回用户友好型和可读性较强的字符串类型。
    repr()面对python解释器,目的是准确性,返回表示python解释器内部的含义,常作为debug用途

  2. 解释器中直接输入obj默认调用repr()函数, print(obj)则调用str()函数

  3. repr()的返回值一般可以用eval()函数来还原对象,一般有如下等式:
    obj == eval(repr(obj)) # 不是绝对成立

  4. 分别调用内建的__str__()和__repr__().
    一般类中要定义__repr__().
    如果类中没有定义__str__(),则调用__repr__()

PS: 实现__repr__()时最好保证其返回值可以用eval()方法将对象重新还原

class A:
    def __str__(self):
        return "str"

    def __repr__(self):
        return "repr"


a = A()
print(a)  # str
print(repr(a))  # repr

回到目录

staticmethod和classmethod的使用场景

class Fruit:
    def __init__(self, num):
        self.num = num

    @staticmethod
    def Init_Num(num):
        num = num
        fruit = Fruit(num)  # 只会返回Fruit
        return fruit


class Apple(Fruit):
    pass


class Banana(Fruit):
    pass


app1 = Apple(20)
ban1 = Banana.Init_Num(20)  # 返回的是Fruit实例

print(isinstance(app1, Apple))  # True
print(isinstance(ban1, Banana))  # False, ban1是Fruit实例
class Fruit:
    def __init__(self, num):
        self.num = num

    @classmethod
    def Init_Num(cls, num):
        num = num
        fruit = cls(num)  # 根据调用者的类型进行返回
        return fruit


class Apple(Fruit):
    pass


class Banana(Fruit):
    pass


app1 = Apple(20)
ban1 = Banana.Init_Num(20)  # 返回的是自身的实例 

print(isinstance(app1, Apple))  # True
print(isinstance(ban1, Banana))  # True

静态方法在类中定义,可以有效的将代码组织起来
回到目录


回到目录

字符串

  • 判定是否包含子串的判定不推荐调用find()族, index()族
    前者找不到返回-1, 后者找不到抛异常ValueError
  • 推荐使用innot in
s = "haha ha ni zai shuo shenme ne?"
if 'haha' in s:
    print('haha is here')

if 'ha' in s:
    print("ha is here")

回到目录

sort()和sorted()

  1. sort() 一般作用于列表,元组不可。
  2. sort()是原址更改,返回值为None
    sorted()返回一个排序后的列表,原有列表不变
  3. 传入参数key比参数cmp效率高。前者针对每个元素仅作一次处理,后者需要调用多次。

回到目录

使用copy模块深拷贝对象

浅拷贝, 拷贝时遇见引用也仅仅拷贝地址。会被其他引用对象影响
深拷贝,拷贝时遇见引用会递归拷贝,不会被其他引用对象影响。

import copy


class Student:
    def __init__(self, name, course=None):
        self._name = name
        if course is None:
            course = []
        self._course = course

    def add_course(self, item):
        self._course.append(item)

    def get_course(self):
        return self._course


jack = Student("jack")
jack.add_course("math")
print(jack.get_course())  # ['math']

lee = copy.copy(jack)  # 浅拷贝
print(lee.get_course())  # ['math']
lee.add_course("chinese")
print(lee.get_course())  # ['math', 'chinese']

print(jack.get_course())  # ['math', 'chinese']

print("*"*50)
# 深拷贝
jack = Student("jack")
jack.add_course("math")
print(jack.get_course())  # ['math']

lee = copy.deepcopy(jack)  # 深拷贝
print(lee.get_course())  # ['math']
lee.add_course("chinese")
print(lee.get_course())  # ['math', 'chinese']

print(jack.get_course())  # ['math']

回到目录

使用Counter

from collections import Counter

三种初始化

a = "ashfkoahkjdg"
b = {'a': 1, "b": 2, 3: 3}


aa = Counter(a)  # 可迭代参数(list、string)
bb = Counter(b)  # 字典
cc = Counter(s=1, a=3, d=9)  # 关键字参数

print(aa)
# Counter({'a': 2, 'h': 2, 'k': 2, 's': 1, 'f': 1, 'o': 1, 'j': 1, 'd': 1, 'g': 1})
print(bb)
# Counter({3: 3, 'b': 2, 'a': 1})
print(cc)
# Counter({'d': 9, 'a': 3, 's': 1})

Counter(obj).elements() 获取Counter中的key值

print(list(aa.elements()))  # ['a', 'a', 's', 'h', 'h', 'f', 'k', 'k', 'o', 'j', 'd', 'g']
print(list(bb.elements()))  # ['a', 'b', 'b', 3, 3, 3]
print(list(cc.elements()))  # ['s', 'a', 'a', 'a', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd', 'd']

# Counter(obj).most_common(n) 找出前n个频率最高的元素和对应的次数

print(aa.most_common(2))  # [('a', 2), ('h', 2)]
print(bb.most_common(2))  # [(3, 3), ('b', 2)]

访问不存在的key时,返回0而不是异常

print(aa["sakjghfkagkjfa"])  # 0

# Counter(obj).update()并没有替换而是添加进原有的计数器对象

aa.update(s=30)  
print(aa)# Counter({'s': 31, 'a': 2, 'h': 2, 'k': 2, 'f': 1, 'o': 1, 'j': 1, 'd': 1, 'g': 1})

# Counter(obj).subtract()对计数器对象中元素统计值相减

aa.subtract("ssss")
print(aa)  # Counter({'s': 27, 'a': 2, 'h': 2, 'k': 2, 'f': 1, 'o': 1, 'j': 1, 'd': 1, 'g': 1})

回到目录

多个库的使用

  • 序列化: 简单使用json 复杂使用cPickle
    pickle的限制: 不能保证操作的原子侠
    存在安全性问题。可以继承类pickle.Unpickler重写find_class()方法
    不能兼容其他语言。
    json在序列化datetime会抛出TypeError异常 需要对json的JSONEncoder进行扩展

  • 获取栈信息
    使用traceback获取栈信息

  • 日志
    使用logging
    建议:
    1. 为logging取一个名字而不是默认。比如使用 代码块2 中的代码为自己模块的logger命名,在main.py中获取本模块的logger
    2. 命名规则: 以模块或者class命名。 遵循"."划分。 根是root logger, logger a.b的父logger对象是a
    3. logging只是线程安全。不支持 多进程 写入同一个日志文件。对于多个进程需要配置不同的日志文件。
    例子:
    4x

    # moduleA.py
    import logging
    logging.basicConfig(level=logging.DEBUG)
    logger = logging.getLogger(__name__)  # 新建一个命名logger
    print(id(logger))
    
    • main.py
    import moduleA
    import logging
    
    logging.basicConfig(level=logging.DEBUG)
    logger = logging.getLogger(moduleA.__name__)  # 调用其他模块的logger
    print(id(logger))
    logger = logging.getLogger(__name__)  # 调用本模块的logger
    print(id(logger))
    

    输出

    322481229720
    322481229720
    322481067904
    

    回到目录

  • 多线程
    建议使用threading而不是_thread
    原因:
    1. threading基于_thread封装,除非特殊需要,不使用多线程底层支持模块的_thread
    2. threading模块对同步原语的支持更加完善和丰富。
    3. threading模块在主线程和子线程交互上更加友好。threading.join()
    4. threading支持守护进程。

  • 使用Queue使多线程编程更加安全

回到目录

猜你喜欢

转载自blog.csdn.net/abc666666_6/article/details/86486187