一、基本概念
# 面向对象编程
class Washer():
# 类初始化函数,初始化类时会被执行
def __init__(self, width, height) -> None:
print('~' * 50)
print(f'洗衣机的尺寸为:{width}, {height}')
# 当print(类实例化对象)时,输出的信息
def __str__(self) -> str:
return '这是一台洗衣机'
# 当删除类实例化对象或类执行完成,会调用删除方法
def __del__(self):
print('洗衣机被销毁')
print('~' * 50)
haier1 = Washer(550, 800)
print(haier1)
二、案例:烤地瓜
需求:根据烤的时间判断红薯生熟程度,可以自己添加调料
# 烤地瓜
class RoastSweetPotatoes():
def __init__(self) -> None:
self.time = 0
self.static = ''
self.seasonings = []
def roast(self, time):
self.time += time
if self.time < 3:
self.static += f'这个地瓜刚烤了{self.time}分钟了,还是生的,只能喂兔子。添加了调料:'
elif self.time < 5:
self.static += f'这个地瓜烤了{self.time}分钟了,半生不熟的,锻炼牙口呢。添加了调料:'
elif self.time < 8:
self.static += f'这个地瓜烤了{self.time}分钟了,刚刚好!请品尝吧!添加了调料:'
else:
self.static += f'这个地瓜已经烤了{self.time}分钟了,已经糊了,狗都不吃了。添加了调料:'
def add_seasonings(self, seasoning):
self.seasonings.append(seasoning)
self.static += f'{seasoning}.'
def __str__(self) -> str:
return self.static
rsp1 = RoastSweetPotatoes()
rsp1.roast(2)
print(rsp1)
rsp2 = RoastSweetPotatoes()
rsp2.roast(4)
rsp2.add_seasonings('糖')
print(rsp2)
rsp3 = RoastSweetPotatoes()
rsp3.roast(6)
rsp3.add_seasonings('糖')
rsp3.add_seasonings('盐')
print(rsp3)
rsp4 = RoastSweetPotatoes()
rsp4.roast(9)
rsp4.add_seasonings('糖')
rsp4.add_seasonings('盐')
rsp4.add_seasonings('酱油')
rsp4.add_seasonings('胡椒粉')
print(rsp4)
三、案例:搬家具
需求:将小于房子剩余面积的家具搬到房子中
"""
搬家具
需求:将小于房子剩余面积的家具搬到房子中
"""
class House():
def __init__(self, address, area) -> None:
self.address = address
self.area = area
self.free_area = area
self.furnitures = []
def pose_furniture(self, furniture):
if self.free_area < furniture.area:
print(f'{furniture.name}面积{furniture.area}平方米,大于房屋剩余面积{self.free_area}平方米,放不下了。')
else:
self.free_area -= furniture.area
self.furnitures.append(furniture.name)
def __str__(self) -> str:
return f'房屋坐落于{self.address},建筑面积:{self.area}平方米,剩余面积{self.free_area}平方米, 已经摆放家具:{self.furnitures}'
class Furniture():
def __init__(self, name, area) -> None:
self.name = name
self.area =area
def __str__(self) -> str:
return f'家具:{self.name},面积:{self.area}平方米,'
print('~~~~~~~~~~~建房子')
house = House('东岳大街', 120)
print(house)
desk = Furniture('桌子', 10)
print(f'~~~~~~~~~~~搬家具{desk.name}')
print(desk)
house.pose_furniture(desk)
print(house)
bed = Furniture('床', 20)
print(f'~~~~~~~~~~~搬家具{bed.name}')
print(bed)
house.pose_furniture(bed)
print(house)
cabinet = Furniture('柜子', 30)
print(f'~~~~~~~~~~~搬家具{cabinet.name}')
print(cabinet)
house.pose_furniture(cabinet)
print(house)
tvset = Furniture('电视机', 61)
print(f'~~~~~~~~~~~搬家具{tvset.name}')
print(tvset)
house.pose_furniture(tvset)
print(house)
四、继承
4.1 继承的概念
Python面向对象的继承指的是多个类之间的所属关系,即子类默认继承父类的所有属性和方法。
在Python中,所有类默认继承object类, object类是顶级类或基类;其他子类叫做派生类。
4.2 单继承:一个父类
"""
单继承 一个父类
"""
class Master(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜大师'
def cook(self):
print(f'满汉全席上桌了!')
class Follower(Master):
pass
xiaoyue = Follower()
print(f'弟子的技能:{xiaoyue.skill}')
xiaoyue.cook()
4.3 多继承:多个父类
"""
多继承:多个父类
"""
class Master(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜'
def cook(self):
print(f'满汉全席上桌了!')
class CookSchool(object):
def __init__(self) -> None:
super().__init__()
self.skill = '川菜'
self.special_skill = '粤菜'
def cook(self):
print('辣死人不偿命了!')
class Follower(Master, CookSchool):
# 父类中有同名属性,按从左向右的顺序,继承第一个找到的属性
# 父类中有同名方法,按从左向右的顺序,执行第一个找到的方法
pass
xiaozhang = Follower()
print(xiaozhang.skill)
print(xiaozhang.special_skill)
xiaozhang.cook()
4.4 子类重写父类的同名属性和方法
属性和方法查找顺序:当前子类 -> 第一父类 -> 第二父类 -> …
"""
子类重写父类的同名属性和方法
属性和方法查找顺序:当前子类 -> 第一父类 -> 第二父类 -> ...
"""
class Master(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜'
def cook(self):
print(f'满汉全席上桌了!')
class CookSchool(object):
def __init__(self) -> None:
super().__init__()
self.skill = '川菜'
self.special_skill = '粤菜'
def cook(self):
print('辣死人不偿命了!')
class Follower(Master, CookSchool):
def __init__(self) -> None:
super().__init__()
self.skill = '粤菜大师'
def cook(self):
print('粤菜好吃不知道名!')
zhangsan = Follower()
print(zhangsan.skill)
zhangsan.cook()
4.5 拓展:mro 顺序 print(Curclass.mro)
"""
子类重写父类的同名属性和方法
"""
class Master(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜'
def cook(self):
print(f'满汉全席上桌了!')
class CookSchool(object):
def __init__(self) -> None:
super().__init__()
self.skill = '川菜'
self.special_skill = '粤菜'
def cook(self):
print('辣死人不偿命了!')
class Follower(Master, CookSchool):
def __init__(self) -> None:
super().__init__()
self.skill = '粤菜大师'
def cook(self):
print('粤菜好吃不知道名!')
zhangsan = Follower()
print(zhangsan.skill)
zhangsan.cook()
# mro 顺序(属性和方法查找顺序):当前子类 -> 第一父类 -> 第二父类 -> ... -> object
print(Follower.__mro__)
4.6 子类调用父类的同名属性和方法
"""
子类调用父类的同名属性和方法
在子类方法中,初始化父类,调用父类方法
"""
class Master(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜'
def cook(self):
print(f'满汉全席上桌了!')
class CookSchool(object):
def __init__(self) -> None:
super().__init__()
self.skill = '川菜'
self.special_skill = '粤菜'
def cook(self):
print('辣死人不偿命了!')
class Follower(Master, CookSchool):
def __init__(self) -> None:
super().__init__()
self.skill = '粤菜大师'
def cook(self):
self.__init__() # 如果省略,属性值是上次调用的值,无法保证是自己的值
print('粤菜好吃不知道名!')
def master_cook(self):
Master.__init__(self) # 父类方法调用前,需要初始化属性值,同时需要加self,代表子类用父类的方法工作
Master.cook(self)
def schoole_cook(self):
CookSchool.__init__(self) # 父类方法调用前,需要初始化属性值,同时需要加self,代表子类用父类的方法工作
CookSchool.cook(self)
zhangsan = Follower()
print(zhangsan.skill)
zhangsan.cook()
zhangsan.master_cook()
zhangsan.schoole_cook()
4.7 多层继承
"""
多层继承
"""
class Master(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜'
def cook(self):
print(f'满汉全席上桌了!')
class CookSchool(object):
def __init__(self) -> None:
super().__init__()
self.skill = '川菜'
self.special_skill = '粤菜'
def cook(self):
print('辣死人不偿命了!')
class Follower(Master, CookSchool):
def __init__(self) -> None:
super().__init__()
self.skill = '粤菜大师'
def cook(self):
self.__init__() # 如果省略,属性值是上次调用的值,无法保证是自己的值
print('粤菜好吃不知道名!')
def master_cook(self):
Master.__init__(self) # 父类方法调用前,需要初始化属性值,同时需要加self,代表子类用父类的方法工作
Master.cook(self)
def schoole_cook(self):
CookSchool.__init__(self) # 父类方法调用前,需要初始化属性值,同时需要加self,代表子类用父类的方法工作
CookSchool.cook(self)
class GrandSon(Follower):
pass
xiaozhangsan = GrandSon()
print(xiaozhangsan.skill)
xiaozhangsan.cook()
xiaozhangsan.master_cook()
xiaozhangsan.schoole_cook()
print(GrandSon.__mro__)
4. 8 super() 调用父类
"""
super()调用父类方法,适用于单继承
"""
class GrandMaster(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜大师'
def cook(self):
print(f'八大盘上席了!')
class Master(GrandMaster):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜粤菜大师'
def cook(self):
print(f'满汉全席上桌了!')
def grandmaster_cook(self):
super().__init__()
super().cook()
class Follower(Master):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁川粤菜杂家'
def cook(self):
print('鲁川粤菜大杂烩上桌了')
def master_cook(self):
super().__init__()
super().cook()
super().grandmaster_cook()
zhangsan = Follower()
print(zhangsan.skill)
zhangsan.cook()
zhangsan.master_cook()
4.9 私有权限
4.9.1 私有属性和私有方法
在 Python中,可以为实例属性和方法设置私有权限,即设置某个实例属性或实例方法不继承给子类。
设置私有权限的方法:在属性名和方法名前面加上两个下划线__
"""
私有属性和私有方法
"""
class GrandMaster(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜大师'
self.__house = '北京四合院' # 定义私有属性,不能继承给子类
def cook(self):
print(f'八大盘上席了!')
class Master(GrandMaster):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜粤菜大师'
def __cook(self): # 定义私有方法,不能继承给子类
print(f'满汉全席上桌了!')
def grandmaster_cook(self):
super().__init__()
super().cook()
class Follower(Master):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁川粤菜杂家'
def cook(self):
print('鲁川粤菜大杂烩上桌了')
def master_cook(self):
super().__init__()
Master().cook() # 找不到父类Master().__cook(),找到的是祖父类GrandMaster().cook()
super().cook() # 找不到父类Master().__cook(),找到的是祖父类GrandMaster().cook()
super().grandmaster_cook()
zhangsan = Follower()
print(zhangsan.skill)
# print(zhangsan.__house) # 没有这个属性
zhangsan.cook()
zhangsan.master_cook()
4.9.2 子类获取(get)和修改(set)父类私有属性
在 Python中,一般定义函数名get_xx用来获取私有属性,定义set_xx用来修改私有属性值。
子类获取(get)和修改(set)父类私有属性,父类定义get和set公有函数,子类实例化对象继承调用该公有方法
"""
子类获取(get)和修改(set)父类私有属性和私有方法,父类定义get和set公有函数,子类实例化对象继承调用该公有方法
"""
class GrandMaster(object):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜大师'
self.__house = '北京四合院' # 定义私有属性,不能继承给子类
def cook(self):
print(f'八大盘上席了!')
def get_house(self):
return self.__house
def set_house(self):
self.__house = '四合院拆迁为10套北京三环10套100平米商品房。'
return self.__house
class Master(GrandMaster):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁菜粤菜大师'
def __cook(self): # 定义私有方法,不能继承给子类
print(f'满汉全席上桌了!')
def grandmaster_cook(self):
super().__init__()
super().cook()
class Follower(Master):
def __init__(self) -> None:
super().__init__()
self.skill = '鲁川粤菜杂家'
def cook(self):
print('鲁川粤菜大杂烩上桌了')
def master_cook(self):
super().__init__()
Master().cook() # 找不到父类Master().__cook(),找到的是祖父类GrandMaster().cook()
super().cook() # 找不到父类Master().__cook(),找到的是祖父类GrandMaster().cook()
super().grandmaster_cook()
zhangsan = Follower()
# print(zhangsan.__house) # 没有这个属性
print(zhangsan.get_house())
print(zhangsan.set_house())
五、多态
5.1 面向对象三大特征:封装、继承、多态
5.1.1 类的封装
- 将属性和方法书写到类的里面的操作即为封装
- 封装可以为属性和方法添加私有权限
5.1.2 类的继承
- 子类默认继承父类的所有属性和方法
- 子类可以重写父类属性和方法
5.1.3 类的多态
- 传入不同的对象,产生不同的结果
5.2 多态
5.2.1 了解多态
多态指的是一类事物有多种形态,(一个抽象类有多个子类,因而多态的概念依赖于继承)。
- 定义:多态是一种使用对象的方式,子类重写父类方法,调用不同子类对象的相同父类方法,可以产生不同的执行结果
- 好处:调用灵活,有了多态,更容易编写出通用的代码,做出通用的编程,以适应需求的不断变化!
- 实现步骤:
定义父类,并提供公共方法
定义子类,并重写父类方法
传递子类对象给调用者,可以看到不同子类执行效果不同
5.2.2 代码实现多态
"""
多态
"""
class Dog(object):
def __init__(self) -> None:
super().__init__()
def work(self):
print(f'汪汪')
class ArmyDog(Dog):
def __init__(self) -> None:
super().__init__()
def work(self):
print(f'攻击指定目标!')
class DrugDog(Dog):
def __init__(self) -> None:
super().__init__()
def work(self):
print('搜索毒品')
class Person(object):
def __init__(self) -> None:
super().__init__()
def work(self, dog):
dog.work()
xiaoli = Person()
ad = ArmyDog()
dd = DrugDog()
xiaoli.work(ad)
xiaoli.work(dd)
5.3 类属性和实例属性
5.3.1 设置和访问类属性
- 类属性就是类对象所拥有的属性,它被该类的所有实例对象所共有。
- 类属性可以使用类对象或实例对象访问。
- 类属性的优点:
①记录的某项数据始终保持一致时,则定义类属性。
②实例属性要求每个对象为其单独开辟一份内存空间来记录数据,而类属性为全类所共有,仅占用一份内存,更加节省内存空间。
"""
设置和访问类属性
"""
class Dog(object):
foots = 4 # 设置类属性
d1 = Dog()
d2 = Dog()
print(d1.foots) # 访问类属性
print(d2.foots) # 访问类属性
print(Dog.foots) # 访问类属性
5.3.2 修改类属性和实例属性
类属性只能通过类对象修改,不能通过实例对象修改,如果通过实例对象修改类属性,表示的是创建了一个实例属性。
"""
修改类属性
"""
class Dog(object):
foots = 4 # 设置类属性
d1 = Dog()
d2 = Dog()
print(d1.foots) # 访问类属性
print(d2.foots) # 访问类属性
print(Dog.foots) # 访问类属性
Dog.foots = 6 # 修改类属性
print(d1.foots) # 访问类属性
print(d2.foots) # 访问类属性
print(Dog.foots) # 访问类属性
d1.foots = 8 # 修改实例属性
print(d1.foots) # 访问类属性
print(d2.foots) # 访问类属性
print(Dog.foots) # 访问类属性
5.7 类方法和静态方法
5.7.1 类方法
- 类方法需要用装饰器@classmethod来标识其为类方法,对于类方法,第一个参数必须是类对象,一般以cls作为第一个参数。
- 当方法中需要使用类对象(如访问私有类属性等)时,定义类方法
- 类方法一般和类属性配合使用
"""
类方法
"""
class Dog(object):
__foots = 4 # 设置类私有属性
@classmethod
def get_foots(cls):
return cls.__foots
d1 = Dog()
print(d1.get_foots())
5.7.2 静态方法
- 需要通过装饰器@staticmethod来进行修饰,静态方法既不需要传递类对象也不需要传递实例对象(形参没有self/cls)
- 静态方法也能够通过实例对象和类对象去访问。
- 当方法中既不需要使用实例对象(如实例对象,实例属性),也不需要使用类对象(如类属性、类方法、创建实例等)时,定义静态方法
- 取消不需要的参数传递,有利于减少不必要的内存占用和性能消耗
"""
静态方法
"""
class Dog(object):
@staticmethod
def info():
print('这是一个标本狗!')
d1 = Dog()
d1.info()
Dog.info()
六、异常
- 了解异常
- 捕获异常 try-except-finally
- 异常的else
- 异常finally
- 异常的传递
- 自定义异常
6.1 了解异常
当检测到一个错误时,解释器就无法继续执行了,反而出现了一些错误的提示,这就是所谓的"异常"。例如:以r方式打开一个不存在的文件。
6.2 异常的写法
"""
异常
"""
try:
f = open('test.txt', 'r')
print(f'以只读模式打开test.txt文件')
except:
f = open('test.txt', 'w')
print(f'以写入模式打开test.txt文件')
finally:
print('文件打开成功')
6.2.1 捕获指定异常
语法
try:
可能发生错误的代码
except 异常类型:
如果捕获到该异常,执行的代码
try:
print(a)
except NameError:
print('变量名有误')
注意:
1.如果尝试执行的代码的异常类型和要捕获的异常类型不一致,则无法捕获异常。
2.一般try下方只放一行尝试执行的代码。
6.2.2 捕获多个指定异常
当捕获多个异常时,可以把要捕获的异常类型的名字,放到 except后,并使用元组的方式进行书写。
try:
print(a)
print(1/0)
except (NameError, ZeroDivisionError):
print('有错误')
6.2.3 捕获异常描述信息
try:
# print(a)
print(1/0)
except (NameError, ZeroDivisionError) as e:
print(e)
6.2.4 捕获所有异常
Exception是所有程序异常类的父类。
try:
# print(a)
print(1/0)
except Exception as e:
print(e)
6.3 异常的else
try:
# print(a)
# print(1/0)
print('你太牛了')
except Exception as e:
print(e)
else:
print(f'完美,没有异常')
6.4 异常finally
try:
f = open('test.txt', 'r')
print(f'以只读模式打开test.txt文件')
except Exception as e:
f = open('test.txt', 'w')
print(f'以写入模式打开test.txt文件')
else:
print(f'完美,没有异常')
finally:
f.close
print('文件关闭')
6.5 异常的传递
体验异常传递
需求:
1.尝试只读方式打开test.txt文件,如果文件存在则读取文件内容,文件不存在则提示用户即可。
2.读取内容要求:尝试循环读取内容,读取过程中如果检测到用户意外终止程序,则 except捕获异常并提示用户。
import time
try:
f = open('test.txt', 'r')
print(f'以只读模式打开test.txt文件')
try:
while True:
content = f.readline()
if len(content) == 0:
break
time.sleep(2)
print(content)
except Exception as e:
print(e)
except Exception as e:
print(e)
else:
print(f'完美,没有异常')
finally:
f.close
print('文件关闭')
6.6 自定义异常
在 Python中,抛出自定义异常的语法为 raise异常类对象。
需求:密码长度不足,则报异常(用户输入密码,如果输入的长度不足3位,则报错,即抛出自定义异常,并捕获该异常)。
"""
自定义异常
"""
# 自定义异常类,继承Exception
class ShortInputError(Exception):
def __init__(self, length, min_len, *args: object) -> None:
super().__init__(*args)
self.length = length
self.min_len = min_len
#设置抛出异常的描述信息
def __str__(self) -> str:
return f'你输入的长度{self.length}小于最小长度{self.min_len}'
def main():
try:
con = input('请输入密码')
min_len = 6
if len(con) < min_len:
raise ShortInputError(len(con), min_len)
except Exception as e:
print(e)
else:
print('密码输入完成')
main()
七、模块和包
- 了解模块
- 导入模块
- 制作模块
- all
- 包的使用方法
7.1 了解模块
Python模块(Module),是一个 Python文件以py结尾,包含了 Python对象定义和 Python语句。
模块能定义函数,类和变量,模块里也能包含可执行的代码。
7.2 导入模块
7.2.1 导入模块的方法
- import 模块名
- from 模块名 import 功能名
- from 模块名 import *
- import 模块名as 别名
- from 模块名 import 功能名 as 别名
7.2.1.1 import 模块名
import math
print(math.sqrt(9))
7.2.1.2 from 模块名 import 功能名
from math import sqrt
print(sqrt(9))
7.2.1.3 from 模块名 import *
from math import *
print(sqrt(9))
7.2.1.4 import 模块名 as 别名
import math as mt
print(mt.sqrt(9))
7.2.1.5 from 模块名 import 功能名 as 别名
from math import sqrt as sq
print(sq(9))
7.3 制作模块
在Python中,每个Python文件都可以作为一个模块,模块的名字就是文件的名字。也就是说自定义模块名必须要符合标识符命名规则。
7.3.1 定义模块
新建一个 Python文件,命名为my_module11.py,并定义一个函数。
def test(a, b):
return a + b
7.3.2 测试模块
在实际开中,当一个开发人员编写完一个模块后为了让模块能够在项目中达到想要的效果,这个开发人员会自行在py文件中添加一些测试信息,例如在my_module11.py文件中添加测试代码。
def test(a, b):
return a + b
# __name__ 在模块内部调用就是 '__main__'
# __name__ 在其他模块调用本模块时就是 本文件的文件名
if __name__ == '__main__': # 模块内部调用
print(test(5, 6))
7.3.3 导入使用模块
import my_module11
my_module11.test(10, 6)
7.3.4 模块定位顺序
当导入一个模块, Python解析器对模块位置的搜索顺序是:
1.当前目录
2.如果不在当前目录, PythonP则搜索在hell变量下的每个目录。
3.如果都找不到, Python会查看默认路径。Linux下,默认路径一般为/usr/local/lib/python/
模块搜索路径存储在 system模块的sys.path变量中。变量里包含当前目录, PYTHONPATH和由安装过程决定的默认目录。
注意
- 自己的文件名不要和已有模块名重复,否则导致模块功能无法使用
- 使用from 模块名 import 功能 的时候如果功能名字重复,调用到的是最后定义或导入的功能。
def sleep(num):
print(f'我是自定义的sleep!{num}')
from time import sleep
sleep(2)
print('done!')
"""
使用from 模块名 import 功能 的时候如果功能名字重复,调用到的是最后定义或导入的功能。
"""
from time import sleep
def sleep(num):
print(f'我是自定义的sleep!{num}')
sleep(2)
print('done!')
7.3.5 模块名字重复的严重性
Python中,数据都是通过引用传递的。
import time
print(time)
time = 1
print(time)
7.4 all
如果一个模块文件中有__a11__变量,当使用 from xxx import * 导入时,只能导入这个列表中的元素。
__all__ = ['testA']
def testA(): # from my_module11 import * 时,可以被调用
pass
def testB(): # from my_module11 import * 时,没法被调用
pass
7.5 包的使用方法
包将有联系的模块组织在一起,即放到同一个文件夹下,并且在这个文件夹创建一个名字为__init__.py文件,那么这个文件夹就称之为包。
7.5.1 制作包
[New]->[Python Package]->输入包名->[OK]->新建功能模块(有联系的模块)
注意:新建包后,包内部会自动创建__init__.py文件,这个文件控制着包的导入行为。
7.5.2 导入包
7.5.2.1 方法一: from 包名 import 模块名
# my_module1
print(1)
def info():
print('my_module1')
# my_module2
print(2)
def info():
print('my_module2')
import mypackage.my_module1 as m1
m1.info()
import mypackage.my_module2 as m2
m2.info()
import mypackage.my_module1
mypackage.my_module1.info()
import mypackage.my_module2
mypackage.my_module2.info()
7.5.2.2 方法二: from 包名 import *
注意:必须在__init__.py文件中添加all=[],控制允许导入的模块列表。
__all__ = ['my_module1', 'my_module2']
# my_module1
print(1)
def info():
print('my_module1')
# my_module2
print(2)
def info():
print('my_module2')
from mypackage import *
my_module1.info()
my_module2.info()