目录
????????代码世界有很多令人大呼小叫的技巧🎅🎅!有的代码像🎉魔术师🎉一样巧妙地隐藏了自己,有的像🌟魔法师🌟一样让你眼花缭乱,还有的像💪???????瑜伽大师?💪???????一样灵活自如。它们让我们惊叹不已,让我们觉得自己仿佛置身于编码的魔幻世界。今天就来分享我见过的令人🎃???????膛目结舌🎃??????????????的代码技巧吧!
> 提醒:如若大佬亲临本文,请在评论区留下足迹,让我这个小白膜拜一下。
?
????????其实,说到代码技巧,不一定是什么惊艳的花式操作,整齐规范的命名,恰到好处的注释,合理的封装类,甚至封装功能文件,封装钩子等等都是让我这代码小白直呼NB的 代码技巧。本篇就来讲解下我在职场中遇到的python代码技巧。
????????前两天看到#IT圈茶余饭后的“鄙视链”#活动里的一个博主讲的编程语言也有鄙视链:
- C 语言工程师鄙视 C++ 工程师。
- C++ 工程师鄙视 Java 和 C# 工程师。
- Java 工程师和 C# 工程师则互相鄙视。
- 而 C# 工程师又鄙视 Visual Basic 工程师和会把 C# 念成「C 井」的工程师,
- 会把 C# 念成「C 井」的工程师则鄙视认为 HTML 是一种程序语言的设计师。
- 用 Python 3 的工程师鄙视还在用 Python 2 的工程师。
......
????????看完这篇文字,再看看自己code文件夹下的一堆.py文件,我的沉默震耳欲聋,哼哼,但是有什么关系,试问目前最火AI领域哪一个不是用python写的?
????????不论是变量命名、还是函数命名、抑或是文件命名都需要能一下子从命名就能看出这个变量或者函数的大致含义。甚至有时规范的命名能帮你有效避免一些bug的出现。
????????如下代码,本来看起来是很正常不过的代码,但是就是因为有一个变量叫mask,另一个算法包也叫mask,这种情况就很不巧,得不到想要的结果是必然的,假如你有一大串代码,你需要从几百行代码里面一行行定位才能找到问题。与其如此,为何不在定义变量的时候就避开这个矛盾呢。
from skimage import io
from pycocotools import mask
def get_area_bbox(label):
encoded_label = mask.encode(label)
area = mask.area(encoded_label)
bounding_box = mask.toBbox(encoded_label)
return area, bounding_box
img = io.imread('/data/img/001.png')
mask = io.imread('/data/label/001.png')
area,bbox = get_area_bbox(mask)#mask变量名称与pycocotools.mask的mask包名称冲突
print(area,bbox)
修改后代码:
from skimage import io
from pycocotools import mask
def get_area_bbox(label):
encoded_label = mask.encode(label)
area = mask.area(encoded_label)
bounding_box = mask.toBbox(encoded_label)
return area, bounding_box
img = io.imread('/data/img/001.png')
label = io.imread('/data/label/001.png')
area,bbox = get_area_bbox(label)#label变量名称与pycocotools.mask的mask包名不冲突
print(area,bbox)
7316 [15, 34, 77, 152]
?
????????python中的@表示修饰符,可以在模块或者类的定义层内对函数进行修饰。用做函数的修饰符,可以在模块或者类的定义层内对函数进行修饰;通常出现在函数定义的前一行,不允许和函数定义在同一行。
? ? ? ?装饰器可以使用在任何一个函数实例之前,?一个修饰符就是一个函数,它将被修饰的函数作为参数,并返回修饰后的同名函数或其他可调用的东西(如果返回不是一个可调用的对象那么会报错)。
#Example 1
def test(func):
print("a")
return func()
@test #从这里可以看出@test等价于 test(xxx())
def xxx():
print('Hello world!')
????????运行结果为:
a
Hello world!
????????通常可以用在一堆相同功能的前面加上装饰器,如下方法使用(每个类中的具体操作省略了没写):
from mmseg.registry import TRANSFORMS
try:
import albumentations
from albumentations import Compose
ALBU_INSTALLED = True
except ImportError:
albumentations = None
Compose = None
ALBU_INSTALLED = False
@TRANSFORMS.register_module()
class ResizeToMultiple(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class Rerange(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class CLAHE(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class RandomCrop(BaseTransform):
return self.__class__.__name__ + f'(crop_size={self.crop_size})'
@TRANSFORMS.register_module()
class RandomRotate(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class RGB2Gray(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class AdjustGamma(BaseTransform):
return self.__class__.__name__ + f'(gamma={self.gamma})'
@TRANSFORMS.register_module()
class SegRescale(BaseTransform):
return self.__class__.__name__ + f'(scale_factor={self.scale_factor})'
@TRANSFORMS.register_module()
class PhotoMetricDistortion(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class RandomCutOut(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class RandomRotFlip(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class RandomFlip(MMCV_RandomFlip):
results['swap_seg_labels'] = self.swap_seg_labels
@TRANSFORMS.register_module()
class Resize(MMCV_Resize):
results[seg_key] = gt_seg
@TRANSFORMS.register_module()
class RandomMosaic(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class GenerateEdge(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class ResizeShortestEdge(BaseTransform):
return self.resize(results)
@TRANSFORMS.register_module()
class BioMedical3DRandomCrop(BaseTransform):
return self.__class__.__name__ + f'(crop_shape={self.crop_shape})'
@TRANSFORMS.register_module()
class BioMedicalGaussianNoise(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class BioMedicalGaussianBlur(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class BioMedicalRandomGamma(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class BioMedical3DPad(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class BioMedical3DRandomFlip(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class Albu(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class ConcatCDInput(BaseTransform):
return repr_str
@TRANSFORMS.register_module()
class RandomDepthMix(BaseTransform):
return results
????????类是python里的一个重要定义,在类的内部,使用 def 关键字来定义方法,与一般函数定义不同,类方法必须第一个参数为 self, self 代表的是类的实例(即你还未创建类的实例),其他参数和普通函数是完全一样。? ? ? ?
????????一般由多个函数组成,并且函数的参数之间有联系建议使用类封装,可以更清晰明了看到类函数之间关系。
class Circle(object):
pi = 3.14 # 类属性
def __init__(self, r):
self.r = r # 实例属性
def get_area(self):
""" 圆的面积 """
# return self.r**2 * Circle.pi # 通过实例修改pi的值对面积无影响,这个pi为类属性的值
return self.r**2 * self.pi # 通过实例修改pi的值对面积我们圆的面积就会改变
circle1 = Circle(1)
print("area:",circle1.get_area()) # 调用方法 self不需要传入参数,不要忘记方法后的括号 输出 3.14
area: 3.14
?????????项目中最常用的地方就是在数据输入的地方:
from mmcv.transforms import LoadAnnotations as MMCV_LoadAnnotations
class LoadAnnotations(MMCV_LoadAnnotations):
"""Load annotations for semantic segmentation provided by dataset.
"""
def __init__(
self,
reduce_zero_label=None,
backend_args=None,
imdecode_backend='pillow',
) -> None:
super().__init__(
with_bbox=False,
with_label=False,
with_seg=True,
with_keypoints=False,
imdecode_backend=imdecode_backend,
backend_args=backend_args)
self.reduce_zero_label = reduce_zero_label
if self.reduce_zero_label is not None:
warnings.warn('`reduce_zero_label` will be deprecated, '
'if you would like to ignore the zero label, please '
'set `reduce_zero_label=True` when dataset '
'initialized')
self.imdecode_backend = imdecode_backend
def _load_seg_map(self, results: dict) -> None:
"""Private function to load semantic segmentation annotations.
Args:
results (dict): Result dict from :obj:``mmcv.BaseDataset``.
Returns:
dict: The dict contains loaded semantic segmentation annotations.
"""
img_bytes = fileio.get(
results['seg_map_path'], backend_args=self.backend_args)
gt_semantic_seg = mmcv.imfrombytes(
img_bytes, flag='unchanged',
backend=self.imdecode_backend).squeeze().astype(np.uint8)
# reduce zero_label
if self.reduce_zero_label is None:
self.reduce_zero_label = results['reduce_zero_label']
assert self.reduce_zero_label == results['reduce_zero_label'], \
'Initialize dataset with `reduce_zero_label` as ' \
f'{results["reduce_zero_label"]} but when load annotation ' \
f'the `reduce_zero_label` is {self.reduce_zero_label}'
if self.reduce_zero_label:
# avoid using underflow conversion
gt_semantic_seg[gt_semantic_seg == 0] = 255
gt_semantic_seg = gt_semantic_seg - 1
gt_semantic_seg[gt_semantic_seg == 254] = 255
# modify if custom classes
if results.get('label_map', None) is not None:
# Add deep copy to solve bug of repeatedly
# replace `gt_semantic_seg`, which is reported in
# https://github.com/open-mmlab/mmsegmentation/pull/1445/
gt_semantic_seg_copy = gt_semantic_seg.copy()
for old_id, new_id in results['label_map'].items():
gt_semantic_seg[gt_semantic_seg_copy == old_id] = new_id
results['gt_seg_map'] = gt_semantic_seg
results['seg_fields'].append('gt_seg_map')
def __repr__(self) -> str:
repr_str = self.__class__.__name__
repr_str += f'(reduce_zero_label={self.reduce_zero_label}, '
repr_str += f"imdecode_backend='{self.imdecode_backend}', "
repr_str += f'backend_args={self.backend_args})'
return repr_str
????????大工程里经常会听到钩子函数(hook function)这个概念,钩子hook,顾名思义,可以理解是一个挂钩,作用是有需要的时候挂一个东西上去。具体的解释是:钩子函数是把我们自己实现的hook函数在某一时刻挂接到目标挂载点上。
????????什么情况下需要实现hook,就是一个功能(类/方法)自身无法满足所有需求,那么可以通过hook 就提供扩展自身能力的可能。举个例子一下就明白了。
????????Programmer类实现三个功能:eat、code、sleep,但程序员也是普通人,不能每天都只吃饭、编码、睡觉,于是通过register_hook() 提供了做别的事情的能力。
import time
class Programmer(object):
"""程序员"""
def __init__(self, name, hook=None):
self.name = name
self.hooks_func = hook
self.now_date = time.strftime("%Y-%m-%d")
def get_to_eat(self):
print(f"{self.name} - {self.now_date}: eat.")
def go_to_code(self):
print(f"{self.name} - {self.now_date}: code.")
def go_to_sleep(self):
print(f"{self.name} - {self.now_date}: sleep.")
def everyday(self):
# 程序员日常三件事
self.get_to_eat()
self.go_to_code()
self.go_to_sleep()
# check the register_hook(hooked or unhooked)
# hooked
if self.hooks_func is not None:
self.hooks_func(self.name)
def play_game(name):
now_date = time.strftime("%Y-%m-%d")
print(f"{name} - {now_date}: play game.")
def shopping(name):
now_date = time.strftime("%Y-%m-%d")
print(f"{name} - {now_date}: shopping.")
if __name__ == "__main__":
# hook 作为参数传入
tom = Programmer("Tom", hook=play_game)
jerry = Programmer("Jerry", hook=shopping)
spike = Programmer("Spike")
# 今日事情
tom.everyday()
jerry.everyday()
spike.everyday()
Tom - 2023-12-18: eat.
Tom - 2023-12-18: code.
Tom - 2023-12-18: sleep.
Tom - 2023-12-18: play game.
Jerry - 2023-12-18: eat.
Jerry - 2023-12-18: code.
Jerry - 2023-12-18: sleep.
Jerry - 2023-12-18: shopping.
Spike - 2023-12-18: eat.
Spike - 2023-12-18: code.
Spike - 2023-12-18: sleep.
?????????hook还有一种用法:定义一个函数,然后作为参数塞到另一个类/方法里。
????????比如以下用法:
def get_path():
"""定义钩子函数"""
path = '/data/images/'
return os.path.listdir(path)
def test(get_path):
# 调用 get_path 钩子函数
num = len(get_path)
print('num of img:',num)
????????两个文件看似没有直接的调用关系,在执行?b.py
?文件时,可以间接的调用?a.py
?文件中的get_path()钩子函数。
num of img: 100
参考链接:python强大的hook函数
????????本文讲解了常见的python代码小技巧——?整齐规范的命名,恰到好处的注释,合理的封装类,封装钩子等等,如果你由更好的python编码技巧,欢迎评论区留下足迹哦。