Python测试Mock x对象遇TypeError:shape为函数而非元组
问题排查与解决:TypeError: can only concatenate tuple (not "function") to tuple
原类方法代码
def augment_data( self, number_of_images_tobe_gen: int = SETTING.AUGMENTATION_SETTING.NUMBER_OF_IMAGES_TOBE_GENERATED, augment_data_address: Path = SETTING.AUGMENTATION_SETTING.AUGMENTED_IMAGES_DIR_ADDRESS, ): """ This function augments the images and save them into the dataset_augmented folder. :param number_of_images_tobe_gen: number of images to be generated :param augment_data_address: address to save the augmented images :return: None """ Augment_data_gen = image.ImageDataGenerator( rotation_range=SETTING.AUGMENTATION_SETTING.ROTATION_RANGE, width_shift_range=SETTING.AUGMENTATION_SETTING.WIDTH_SHIFT_RANGE, height_shift_range=SETTING.AUGMENTATION_SETTING.HEIGHT_SHIFT_RANGE, shear_range=SETTING.AUGMENTATION_SETTING.SHEAR_RANGE, zoom_range=SETTING.AUGMENTATION_SETTING.ZOOM_RANGE, horizontal_flip=SETTING.AUGMENTATION_SETTING.HORIZONTAL_FLIP, fill_mode=SETTING.AUGMENTATION_SETTING.FILL_MODE, ) main_address = ( augment_data_address or SETTING.AUGMENTATION_SETTING.AUGMENTED_IMAGES_DIR_ADDRESS ) for image_category in self.image_dict.keys(): # check if a dir dataset_augmented exists if not create it if not os.path.exists(main_address / image_category): os.makedirs(main_address / image_category) # check if the dir is empty if not delete all the files else: # empty the previous augmented images for file in (main_address / image_category).iterdir(): os.remove(file) number_of_images = self.image_dict[image_category]["number_of_images"] for _ in tqdm( range(0, number_of_images_tobe_gen), desc=f"Augmenting {image_category} images:", ): # generate a random number integer between 0 and number_of_images rand_img_num = int(random.random() * number_of_images) img_address = self.image_dict[image_category]["image_list"][ rand_img_num ] for case in SETTING.IGNORE_SETTING.IGNORE_LIST: if case == img_address.name: logger.debug(f"Found {case} in {image_category} folder") continue # check if the image name is in the ignore list, if so continue if img_address in SETTING.IGNORE_SETTING.IGNORE_LIST: continue logger.debug(f"Image address: {img_address}") img = load_img(img_address) x = img_to_array(img) x = x.reshape((1,) + x.shape) for _ in Augment_data_gen.flow( x, batch_size=SETTING.AUGMENTATION_SETTING.BATCH_SIZE, save_to_dir=main_address / image_category or SETTING.AUGMENTATION_SETTING.AUGMENTED_IMAGES_DIR_ADDRESS / image_category, save_prefix=SETTING.AUGMENTATION_SETTING.AUGMENTED_IMAGES_SAVE_PREFIX, save_format=SETTING.AUGMENTATION_SETTING.AUGMENTED_IMAGES_SAVE_FORMAT, ): break
单元测试代码
class XObjClass: def shape(self): return () def reshape(self, *args, **kwargs): print(args, kwargs) return self def img_to_array_func(*args, **kwargs): print(args, kwargs) return XObjClass def load_image_func(*args, **kwargs): print(args, kwargs) return None class ImageAddressObject: @property def name(self): return "test_image.jpg" def image_dict_object(*args, **kwargs): print(args, kwargs) return { "pdc_bit": {"image_list": [ImageAddressObject], "number_of_images": 0}, "rollercone_bit": {"image_list": [ImageAddressObject], "number_of_images": 0}, } def test_augment_data(mocker, _object): # mocker patch the property categories_name mocker.patch( "neural_network_model.process_data.Preprocessing.categorie_name", new_callable=mocker.PropertyMock, return_value=["pdc_bit", "rollercone_bit"], ) # mocker patch the image dict object mocker.patch( "neural_network_model.process_data.Preprocessing.image_dict", new_callable=mocker.PropertyMock, side_effect=image_dict_object, ) # mocker patch load_image function mocker.patch( "neural_network_model.process_data.load_img", side_effect=load_image_func, ) # mocker patch the img_to_array function mocker.patch( "neural_network_model.process_data.img_to_array", side_effect=img_to_array_func, ) _object.augment_data(number_of_images_tobe_gen=5)
错误信息
x = img_to_array(img) > x = x.reshape((1,) + x.shape) E TypeError: can only concatenate tuple (not "function") to tuple ../neural_network_model/process_data.py:221: TypeError
问题原因
img_to_array_func返回类而非实例:原代码中img_to_array返回numpy数组实例,测试代码里却直接返回XObjClass类,导致x是类对象,x.shape指向类的方法而非属性值。shape定义不符合原代码逻辑:原代码中x.shape是属性(numpy数组的特性),但测试里把shape定义成了方法,即使是实例,访问x.shape也会得到函数对象。- 额外索引隐患:
image_dict中number_of_images设为0,会导致rand_img_num计算结果为0,而image_list里存的是类而非实例,后续访问img_address.name也会出错。
解决方法
修改后的测试代码如下:
class XObjClass: @property def shape(self): return () # 将shape改为属性,匹配numpy数组的行为 def reshape(self, *args, **kwargs): print(args, kwargs) return self def img_to_array_func(*args, **kwargs): print(args, kwargs) return XObjClass() # 返回类的实例,而非类本身 def load_image_func(*args, **kwargs): print(args, kwargs) return None class ImageAddressObject: @property def name(self): return "test_image.jpg" def image_dict_object(*args, **kwargs): print(args, kwargs) return { "pdc_bit": {"image_list": [ImageAddressObject()], "number_of_images": 1}, # 存入实例,同时修正number_of_images避免索引问题 "rollercone_bit": {"image_list": [ImageAddressObject()], "number_of_images": 1}, } def test_augment_data(mocker, _object): # mocker patch the property categories_name mocker.patch( "neural_network_model.process_data.Preprocessing.categorie_name", new_callable=mocker.PropertyMock, return_value=["pdc_bit", "rollercone_bit"], ) # mocker patch the image dict object mocker.patch( "neural_network_model.process_data.Preprocessing.image_dict", new_callable=mocker.PropertyMock, side_effect=image_dict_object, ) # mocker patch load_image function mocker.patch( "neural_network_model.process_data.load_img", side_effect=load_image_func, ) # mocker patch the img_to_array function mocker.patch( "neural_network_model.process_data.img_to_array", side_effect=img_to_array_func, ) _object.augment_data(number_of_images_tobe_gen=5)
修改说明
- 给
XObjClass的shape加上@property装饰器,让它变成属性,匹配原代码中numpy数组的shape访问方式。 img_to_array_func返回XObjClass()实例,确保x是实例对象,能正确调用reshape方法和访问shape属性。image_dict中image_list存入ImageAddressObject实例,同时把number_of_images设为1,避免随机索引计算错误和属性访问错误。
内容的提问来源于stack exchange,提问作者Amin Tash
相关产品推荐
相关产品推荐

