Python Notes

Python notes, avoid redundant searching

大量变量赋值

有时会想到建很多变量之后给他们赋值操作,但是发现这种方式本身无法批量写出,代码又不好写又不可读

  • 结论
    任何管理大量变量并利用其名称的需求都可以利用自建的字典完成(而不是内建的变量空间!)
    相关例子

    不使用

    a = xxx
    b = xxx
    ...

    而是用

    dict={}
    for i in list:
    dict[i] = xxx
  • 失败的尝试

    • 尝试过使用exec()函数,但是其中完成的变量赋值都在其自己的local字典中,如果写在函数里就和没执行一样。详细
    • 尝试过写一个生成脚本的脚本之后执行生成的脚本。可行是可行,但是又慢又麻烦

程序的命令行选项 argparse

python标准库提供了非常方便的argparse库来完成这部分需求。计划在下个版本的CrestSub用用。
相关教程

在使用包含该库的API时可以用parse_args()方法生成需要的命名空间对象。
ArgumentParser.parse_args(args=None, namespace=None)
其默认值从sys.argv获取,此处通过手动传入arg列表参数完成命名空间对象的创建

例:

use_api.py >folded
from package import package_main, build_parser 
#import the ArgumentParser building func
def use_package():
package_parser = build_parser()
args = package_parser.parse_args(['--help','...']) #list of input arguments
package_main(args)

选项值语法

选项和它的值是作为两个单独参数传入

parser.parse_args(['--foo', 'FOO'])

对于长选项(名称长度超过一个字符的选项),选项和值也可以作为单个命令行参数传入,使用 = 分隔它们即可

parser.parse_args(['--foo=FOO'])

对于短选项(长度只有一个字符的选项),选项和它的值可以拼接在一起

parser.parse_args(['-xX'])

有些短选项可以使用单个 - 前缀来进行合并,如果仅有最后一个选项(或没有任何选项)需要值的话

>>> ...
>>> parser.parse_args(['-xyzZ'])
Namespace(x=True, y=True, z='Z')

pip 直接安装的pdb2pqr是不能用的版本 20/12/21

列表排序sort()方法

相关例子
参考

列表取随机元素

random.choice(list_i)
参考

屏蔽函数的标准输出

参考

串行中执行系统指令的方案 subprocess.run()

在串行中需要执行某个系统语句,执行结束后再继续运行剩余的脚本
古老的解决方案是os.system() 仅仅在一个子终端运行系统命令,而不能获取命令执行后的返回信息
相对新的os.popen() 可以返回输出结果,但是难以方便的做到运行结束后再执行下一句,需要使用.wait()方法确保串行时执行结束
3.5之后加入的subprocess.run()函数看起来是目前最优的方案
参考 [文档] (https://docs.python.org/zh-cn/3/library/subprocess.html)
常用选项

run('command', check=True, text=True, shell=True, capture_output=True)

is 和 == 的区别

is是判断id()的返回值是否相同(是否指向同一对象)
==是判断值本身是否相同

python中的缓冲输入输出

stdout & stderr

在测试程序的时候发现自己的日志输出没有及时出现 调查之后发现需要修改export PYTHONUNBUFFERED=1 或在运行是加-u
详解 关于命令行参数

f.write()

发现使用write写入文件是也会被缓冲,无法及时输出
通过在打开文件时设置缓存为0同时用二进制方法写入
参考

原地动态输出(覆盖旧内容)

print(f'Working on {i}/100', end='\r', flush=1)

end='\r'使得光标回到行首 flush刷新输出 使得缓存的输出被写出

x +=1 和 x = x + 1

可变元素的类型中,x +=1x = x+1作用不同,区别如下:
x +=1,修改x自身的值
x = x+1,创建一个新的”同名”对象x,并将x+1,赋值给新创建的同名变量x(它的含义和y = x + 1相同,只不过这里将y取名为x了),x + 1赋值给新x后,旧x的指向就会被释放

给类实现多个构造函数

关键理解: init 是所有构造函数的交集
参考class_structure 以及 网页

使用点解析类中的信息__getattr__,setattr,getattribute

  • getattribute
    访问类中的任何属性的时候触发,全面的控制属性的访问方法
    注意!由于它拦截了对任何属性的访问,所以想要在其中使用类内的属性而不造成无线循环(被访问的属性又会先调用__getattribute__),唯一有效的使用情景必须包含基类的__getattribute__方法
  • getattr
    访问类中没有被定义的属性时触发。(在抛出AttributeError时)
    相比前者可以实用的构建解析类中信息的方法
  • setattr
    尝试通过点解析并给其赋值时
    注意!类内自己给对象赋值也会出发这个方法,所以如果在该方法内给对象赋值就会导致无限循环
    往往通过__dict__属性完成赋值避免这个问题
    参考 文档

类对象在迭代器中实现有限次迭代(迭代有限个元素)

根据python的迭代器原理
迭代器遇到对象返回StopIteration异常之后会终止
于是可以在__getitem__方法中,在迭代次数超过设定值后抛出StopIteration异常

class A:
def __init__(self, a, b):
self.a = a
self.b = b
def __getitem__(self, id):
if id == 0:
return self.a
if id == 1:
return self.b
if id > 1:
raise StopIteration
x = A('1','2')
for i in x:
print(i)

读取文件时判断文件结尾

可以使用

while True:
line = f.readline()
...
if line = '':
break
...

的结构

Numpy 的高级索引

https://numpy.org/doc/stable/reference/arrays.indexing.html#advanced-indexing
https://www.runoob.com/numpy/numpy-advanced-indexing.html

按值删除列表里所有符合的对象

需要通过倒序遍历来避免删除后序号变化带来的问题

for i in range(len(self.residues)-1,-1,-1):
if self.residue[i].name == name:
del self.residues[i]

*基本所有涉及迭代中删除列表元素的问题都要这样遍历

记忆化搜索

问题:
“建立的树想要在操作某个最儿子对象的时候遍历所有最儿子对象,是直接.回去然后遍历好呢还是给最爸爸做个get所有最儿子的方法好呢”
这是平衡 时间 和 空间 资源的消耗。
如果重用的次数比较多(或者其中子部分重用次数多),单次耗时长就时间优先,选择记忆。反之空间优先,选择不记忆。
如果都不重要(往往是迷惑的来源),就以可读性,可扩展性,编写难度优先。(例:Class_Structure.Structure.get_all_protein_atom())

类变量

只有只属于这个类的抽象概念,一定不能是受类对象的具体性决定的变量

初始化函数中初始化对象值

对于python,不需要在构造函数里清理变量,因为是引用传递,会被重复访问的只有类变量。
只有在必需要有初始值时(比如之后要append,或者检查是否为0),建议一起初始化。
如果有些变量只有在某个方法里用到,那么通过方法被访问的频率决定要不要在构造函数里初始化。平衡逻辑和资源。

关于拷贝

浅拷贝:
大部分函数给出新的对象时默认是浅拷贝,即用于构建新对象的其他对象的成员可变对象引用形式传递只有最外层的对象是新对象
深拷贝:
深拷贝的目的是构建一个全新的对象,即递归地将对象内的所有成员可变对象以拷贝的形式传递到新对象中。
在深拷贝中往往存在两种问题:
1)对象的成员对象直接或间接地包含自己(直接递归地拷贝的话会引起无限循环)
2)全部拷贝的话会拷贝不需要的额外内容(例如:每次拷贝应该共享的administrative data structure)
python的copy标准库中的deepcopy函数解决这两个问题的方法分别是:
1)建立一个备忘录临时字典(memo)存储所有拷贝过的对象,对于相同原对象的拷贝请求直接给出已经拷贝过对象的引用。通过这种方法,深拷贝后的对象内部保有原本对象的拓扑结构(即包含关系)。
2)允许用户自定义deepcopy方法
也可以通过在调用deepcopy时向memo中添加{原对象内存id:值}来改写deepcopy的行为。(例:deepcopy(self, memo = {id(self.parent):None})来实现不拷贝祖先节点)

自定义类deepcopy的默认逻辑

copy.py >folded
#简化后的函数定义
def deepcopy(x, memo=None, _nil=[]):
# 递归第一个的情况
if memo is None:
memo = {}
# 判断是否被拷贝过
d = id(x)
y = memo.get(d, _nil)
if y is not _nil:
return y # 被拷贝过就直接给出引用
# 没有被拷贝过则使用reduce+重建来拷贝自身
reductor = getattr(self, "__reduce_ex__", None) # 这个b函数在哪定义的啊!!
rv = reductor(4)
y = _reconstruct(x, memo, *rv)
memo[d] = y
_keep_alive(x, memo) # Make sure x lives at least as long as d
return y

def _reconstruct(x, memo, func, args,
state=None, listiter=None, dictiter=None,
deepcopy=deepcopy):
if args:
args = (deepcopy(arg, memo) for arg in args)
y = func(*args)
memo[id(x)] = y

if state is not None:
state = deepcopy(state, memo)
if isinstance(state, tuple) and len(state) == 2:
state, slotstate = state
else:
slotstate = None
if state is not None:
y.__dict__.update(state)
if slotstate is not None:
for key, value in slotstate.items():
setattr(y, key, value)

if listiter is not None:
for item in listiter:
item = deepcopy(item, memo)
y.append(item)
if dictiter is not None:
for key, value in dictiter:
key = deepcopy(key, memo)
value = deepcopy(value, memo)
y[key] = value
return y

关于赋值

  • 变量赋值: python中对于不可变对象是变量赋值,直接将一个新对象索引到标识符
  • 引用赋值: python中对于可变对象是引用赋值,新标识符和原标识符指向同一个对象,通过任何一个标识符改变该对象的值都会使另一个标识符能访问到的值相应变化(同一物)(所以直接传self的双向链表是可行的)
    参考

关于bound method的赋值

注意如果将一个bound method复制给另一个实例的bound method标识符的时,python的行为很反直觉

如下例:

class A:
def __init__(self, v) -> None:
self.v = v
def report_v(self):
print(self.v)

a = A(1)
b = A(2)

a.report_v()
b.report_v()

b.report_v = a.report_v
b.report_v()

输出为

1
2
1

这里在最后b.report_v()实际调用了a.report_v然后用a的值给出了输出(所以是1而不是2)

这是因为report_v本身带有a作为第一个预载参数而没有换成b

解决方案:

b.report_v = a.report_v.__func__.__get__(b)

使用.__func__.__get__()来重新绑定实例

构造函数的行参默认值被无意修改(!重要的例子!)

在通过
在构造函数中使用可变对象作为形参默认值时,之后对默认初始化的类对象的该变量进行操作时由于是引用赋值,会直接作用在形参默认值那个对象上 导致没法通过构造函数完成彻底初始化

class A:
def __init__(self, x=None, y=[]):
self.x = x
self.y = y #.copy()

for i in range(10):
data = A()
data.x = i
data.y.append(i)

print(data.x, data.y)

>>>> 0 [0]
>>>> 1 [0, 1]
>>>> 2 [0, 1, 2]
>>>> 3 [0, 1, 2, 3]
>>>> 4 [0, 1, 2, 3, 4]
>>>> 5 [0, 1, 2, 3, 4, 5]
>>>> 6 [0, 1, 2, 3, 4, 5, 6]
>>>> 7 [0, 1, 2, 3, 4, 5, 6, 7]
>>>> 8 [0, 1, 2, 3, 4, 5, 6, 7, 8]
>>>> 9 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

这里A()本来应该生成一个x,y都是空对象的新类对象 但是由于y默认形参内存中的对象被改变了,所以上一次循环中y的值被保留了

class A:
def __init__(self, x=None, y=[]):
self.x = x
self.y = y #.copy()
print(y, id(y), id(self.y))

for i in range(10):
data = A()
data.x = i
data.y.append(i)

>>>> [] 2010413471752 2010413471752
>>>> [0] 2010413471752 2010413471752
>>>> [0, 1] 2010413471752 2010413471752
>>>> [0, 1, 2] 2010413471752 2010413471752
>>>> [0, 1, 2, 3] 2010413471752 2010413471752
>>>> [0, 1, 2, 3, 4] 2010413471752 2010413471752
>>>> [0, 1, 2, 3, 4, 5] 2010413471752 2010413471752
>>>> [0, 1, 2, 3, 4, 5, 6] 2010413471752 2010413471752
>>>> [0, 1, 2, 3, 4, 5, 6, 7] 2010413471752 2010413471752
>>>> [0, 1, 2, 3, 4, 5, 6, 7, 8] 2010413471752 2010413471752

如果使用拷贝复制则不会出现这个问题

self.y = y.copy()

>>>> 0 [0]
>>>> 1 [1]
>>>> 2 [2]
>>>> 3 [3]
>>>> 4 [4]
>>>> 5 [5]
>>>> 6 [6]
>>>> 7 [7]
>>>> 8 [8]
>>>> 9 [9]

!重要结论!

在使用可变对象时需要注意辨析需求是拷贝还是引用
注意对于形参默认值的引用引发的问题

一个解决方案

Class A:
def __init__( self, **kwargs ):
self.x = kwargs.get('x', None)
self.y = kwargs.get('y',list())

**kwargs 返回一个字典,get()方法在字典中搜索给定的键,如果没有找到则返回一个默认值
kwarg 还可以用来判断哪些参数没有被输入
可以用字典的方式批量提供关键参数

一些架构的思想

系统学习前的一些想法

  • 可以把大函数分解增加可读性,即使分解出的小函数几乎不会被重用,但可以做成内部函数增加逻辑性 (看别人代码所得)
    同时这种策略可以在处理复杂问题时**“自上而下”**的完成,先假装这些内部函数写好了把上层的函数先写好再慢慢写下层函数 (有什么好处?至少思路更清晰)(很有效)
  • 不要刻意的想着使用某种语法(比如修饰器)思路最重要
  • 有些语言函数只能依托于类存在 类不一定要依托于某种具体的对象,只是为了归类将函数和变量放进类里也是可以的
  • 区分数据结构和继承关系:继承是从一般到特殊,而不是从属关系!数据结构用节点链表的概念理解很有帮助,在class structure中从atom对象访问蛋白就可以通过一级一级的双向链表结合访问函数来实现。
  • 遇到哪些不确定的,不普遍的,可能扩展的,最好能整合起来放在独立的函数、类、模块里
  • 核心还是减少重用,但是是在各个层级的,同时维持逻辑的一致性

关于之后建立图,可以建立新的类:边(连接性) 和 节点(原子)

关于访问url中的内容/从数据库中获取数据

关于各种库的区别

目前来讲urllib3是最好用的 而requests中使用了urllib3并优化了用户体验 参考
request.get()获取对象 对象.text获取文本 对象.content获取byte

request 官方文档
https://docs.python-requests.org/en/latest/

关于BeautifulSoup4

该库解析html为树结构从而便于访问具体内容
obj.标签名 访问子标签
obj.attrs 访问标签内属性列表
obj.find_all()/find() 返回该节点下所有满足条件的节点 (如何只搜索一级?)

关于string和text

obj.string 当tag及其子tag内有唯一的文本是返回该文本 否则返回None
obj.text 返回tag中所有文本 以换行按顺序分割各节点文本

关于previous/next_sibling返回空行

使用obj.find_previous/next_sibling()

next_sibling does not necessarily return the next html tag but instead the next “soup element”. usually that is a only a newline but can be more. find_next_sibling() on the other hand return the next html tag ignoring whitespace and other crud between the tags. 参考

关于类中的各种方法

实例方法
类方法:用来做和所有general类对象相关的一些通用函数
静态方法:用来做用来分解内部流程用的内部函数

Q:实例方法中能调用类方法吗??

关于类嵌套 父类 子类

在EnzyHTP.Config和autodE中使用类嵌套的方式存储参数

子类中访问父类的方式

在子类的类变量的定义中无法访问父类的变量
但是子类的类方法可以访问父类中的成员 可以借此完成层级化的参数配置初始化

class A:
xxx = 1
class B:
yyy=2
# xyy = A.xxx ---> NameError: name 'A' is not defined
@classmethod
def p_p(cls):
# print(cls.yyy, A.xxx) ---> 2 1
cls.xyy = A.xxx
print(cls.yyy, cls.xyy, A.xxx) # ---> 2 1 1

A.B.p_p()

直接在bash shell命令行使用运行python语句

注意区分引号
https://stackoverflow.com/questions/30702519/python-c-vs-python-heredoc
example:

bash:~$ python -c "import argparse
parser = argparse.ArgumentParser()
parser.add_argument('--file_name', type=str, required=True, help='name of python file to be created')
parser.parse_args()" --file_name test

函数兼容不同输入类型执行不同逻辑

使用plum包的dispatch装饰器
从而根据类型注释中的类型决定使用的逻辑
https://blog.csdn.net/weixin_36376765/article/details/113961245

在抽象类中定义接口

https://stackoverflow.com/questions/2736255/abstract-attributes-in-python

pylint输出信息控制

https://pylint.pycqa.org/en/latest/user_guide/messages/message_control.html

为什么要使用namedtuple

https://zhuanlan.zhihu.com/p/33446228

logging中logger的架构和层级关系

_LOGGER = logging.getLogger(name) 会给出一个名称是name的logger

该logger默认是root logger (logging.getLogger("")) 的子logger, 默认继承任何root的行,这样会出现一个潜在问题:

别的使用logging的包如果配置了root logger的行为,就会影响每一个这个包之外的logger

设置_LOGGER.propagate = False可以禁用此行为

相关讨论:https://github.com/python/cpython/issues/80374, https://github.com/ChemBioHTP/EnzyHTP/issues/126

[logging.getLogger("")] + [j for i, j in logging.Logger.manager.loggerDict.items()]可以给出一个包含所有logger的列表