一、面向对象
# 类名使用大驼峰命名法
class Cat:
def eat(self):
print("小猫爱吃鱼")
def drink(self):
print("小猫要喝水")
# 创建猫对象
cat = Cat()
cat.eat() # 小猫爱吃鱼
cat.drink() # 小猫要喝水
print(cat) # <__main__.Cat object at 0x000001E68791A160>
# 创建多个猫对象
lazy_cat = Cat()
lazy_cat.eat()
lazy_cat.drink()
print(lazy_cat) # <__main__.Cat object at 0x00000171CC285F40>
lazy_cat2 = lazy_cat
print(lazy_cat2) # <__main__.Cat object at 0x00000171CC285F40>
# 在类外部给对象增加属性,但不推荐
cat.name = "Tom"
二、在初始化方法内部定义属性
在**init**方法内部使用 self.属性名 = 属性的初始值 就可以定义属性。定义属性后,再使用Cat类创建的对象都会拥有该属性。
# 在初始化方法内部定义属性
class Cat:
# 在创建对象时自动调用该方法
def __init__(self):
# 该方法专门用来定义一个类具有哪些属性
print("这是一个初始化方法")
# 用 Cat 类创建的猫对象都有一个 name 属性,且该属性的初始值为'Tom'
self.name = 'Tom'
def eat(self):
print("%s 爱吃鱼" % self.name)
# 在使用类名创建对象时,会自动执行:1分配空间-创建对象;2为对象设置初始值-初始化方法
tom = Cat() # 这是一个初始化方法
tom.eat() # Tom 爱吃鱼
在以上的案例中有个缺陷,就是不管创建多少只猫,猫的name属性都是”Tom“。为了提高灵活性,对初始化方法进行改造,初始化的同时设置初始值,这里的初始化方法可以理解为Java里类的构造函数。
# 利用参数设置属性初始值
class Cat:
# 在创建对象时自动调用该方法
def __init__(self, name):
# 该方法专门用来定义一个类具有哪些属性
print("这是一个初始化方法")
# 用 Cat 类创建的猫对象都有一个 name 属性,且该属性的初始值为'Tom'
self.name = name
def eat(self):
print("%s 爱吃鱼" % self.name)
# 在使用类名创建对象时,会自动执行:1分配空间-创建对象;2为对象设置初始值-初始化方法
tom = Cat("Tom")
tom.eat()
lazy_cat = Cat("大懒猫")
lazy_cat.eat()
三、内置方法__del__和对象的生命周期
# 利用参数设置属性初始值
class Cat:
# 在创建对象时自动调用该方法
def __init__(self, name):
self.name = name
def eat(self):
print("%s 爱吃鱼" % self.name)
def __del__(self):
print("这是一个销毁方法")
# 在使用类名创建对象时,会自动执行:1分配空间-创建对象;2为对象设置初始值-初始化方法
tom = Cat("Tom")
tom.eat() # Tom 爱吃鱼
lazy_cat = Cat("大懒猫")
lazy_cat.eat() # 大懒猫爱吃鱼
# 若这里删除了tom变量 则会在删除时就调用__del__方法
del tom # 删除tom变量后会自动调用__del__方法 输出“这是一个销毁方法”
print("-" * 30) # 这条代码是在执行 __del__ 前执行,因为 lazy_cat 是全局变量
# 这里才再次输出“这是一个销毁方法”
# __del__ 是在整个程序执行结束后才调用
四、__str__内置方法
在 python 中,用 print 打印对象变量,默认会输出该变量所引用的对象是由哪一个类所创建的对象以及该变量在内存中的地址值。如果想用 print 方法打印自定义的内容时,就可以使用**str**内置方法。__str__方法必须返回一个字符串。
此方法类似于Java中重写toString()方法。
五、封装
将属性和方法封装到一个抽象类中,外界使用类创建对象,让对象调用方法,对象方法的细节都被封装到类的内部,
案例:小明爱跑步
需求:
1.小明体重75.0公斤
2.小明每次跑步会减肥0.5公斤
3.小明每次吃东西体重增加1公斤
# 封装
# 属性:姓名name、体重weight
# 方法:跑步run、吃东西eat
class Person:
def __init__(self, name, weight):
self.name = name
self.weight = weight
def run(self):
print("%s 跑步" % self.name)
self.weight -= 0.5
def eat(self):
print("%s 吃东西" % self.name)
self.weight += 1.0
def __str__(self):
return "%s 体重 %.1f 公斤" % (self.name, self.weight)
person1 = Person("小明", 75.0)
person1.run()
person1.eat()
person1.eat()
person1.eat()
person1.run()
print(person1)
# 小明 跑步
# 小明 吃东西
# 小明 吃东西
# 小明 吃东西
# 小明 跑步
# 小明 体重 77.0 公斤
六、私有属性和方法
类的私有属性和方法是在属性或方法名前添加两个下划线,声明该属性和方法为私有,无法从外部直接访问该属性和方法,但在类内部的方法可以访问该类的私有属性和方法。
class Women:
def __init__(self, name):
self.name = name
self.__age = 18
def secret(self):
# 在对象内部的方法可以正常访问对象的私有属性
print("%s 的年龄是 %d" % (self.name, self.__age))
xiaofang = Women("小芳")
# 无法直接从外部访问到对象的私有属性
# print(xiaofang.__age) # AttributeError: 'Women' object has no attribute '__age'
# 私有方法也无法直接从外部访问
xiaofang.secret()
在python中,其实并没有真正意义上的私有,本质上是解释器对名称做了一些特殊的处理,使得无法从外部直接访问。若还是想访问,则可以通过**_类名__属性名/方法名**去访问。
# 私有属性 外部无法直接访问
print(xiaofang._Women__age) # 18
# 私有方法 外部不能直接调用
print(xiaofang._Women__secret()) # 小芳 的年龄是 18
七、继承
1、继承的概念:继承可以实现代码的重用,相同的代码不需要重复的编写。继承就是子类拥有父类的所有属性和方法。
2、继承的语法:class 类名(父类名):
3、继承的特性:传递性,子类既继承父类的属性和方法,还继承父类的父类的属性和方法。
八、方法重写
子类拥有父类的所有属性和方法,可以直接使用父类中已经封装好的方法,不需要再次开发。但如果父类的方法实现不能满足子类需要时,子类可以对父类的方法进行重写。
重写父类方法时有两种情况:
1、覆盖父类的方法:如果在实际开发中,子类的方法实现和父类的方法实现完全不同,就可以使用覆盖的方式。
2、对父类方法进行扩展:如果在开发中,子类的方法实现包含父类的方法实现,就可以使用扩展的方式。在子类中重写父类的方法:super().父类方法,也可以使用父类名调用父类方法:Dog.父类方法(self)。
九、父类的私有属性和方法
子类对象不能在方法内部直接访问父类的私有属性和方法。子类对象可以通过父类的公有方法间接访问父类的私有属性和方法。
十、多继承
在python中,子类可以拥有多个父类,并且具有所有父类的属性和方法。
注意:父类之间不要有重名的方法或属性。如果父类之间存在同名的属性或方法应该尽量避免使用多继承。
若父类之间出现同名的方法或属性,如何才能确定子类对象调用方法的执行顺序呢?
使用MRO方法确定子类对象调用方法的顺序
class A:
def test(self):
print("这是A类的 test 方法")
def demo(self):
print("这是A类的 demo 方法")
class B:
def demo(self):
print("这是B类的 demo 方法")
def test(self):
print("这是B类的 test 方法")
# 多继承
class C(A, B):
pass
# 创建子类对象
c = C()
c.test() # 这是A类的 test 方法
c.demo() # 这是A类的 demo 方法
# 确定 C 类对象调用方法的顺序
print(C.__mro__)
# (<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class 'object'>)
十一、新式类和经典类
object 是 python 为所有对象提供的基类,提供一些内置的方法和属性,可以使用 dir 函数查看。
新式类:以object为基类的类
经典类:不以object为基类的类
为了保证编写的代码能同时在 python2.x 和 python3.x 运行,在定义类时如果没有父类,建议统一继承 object 。
十二、多态
1、多态的概念
多态:不同的子类对象调用相同的父类方法,产生不同的执行结果。
多态是以继承和重写父类方法为前提。
class Dog(object):
def __init__(self, name):
self.name = name
def game(self):
print("%s 简单玩耍" % self.name)
class XiaoTianQuan(Dog):
def game(self):
print("哮天犬在天上玩耍")
class Person:
def __init__(self, name):
self.name = name
def game_with_dog(self, dog):
print("%s 和 %s 玩" % (self.name, dog.name))
dog.game()
# wangcai = Dog("小黑")
wangcai = XiaoTianQuan("旺财")
xiaoming = Person("小明")
xiaoming.game_with_dog(wangcai)
2、类属性和类方法
(1)类是一个特殊的对象
python中一切皆对象:
class AA: 定义的类属于类对象
obj1 = AA( ): 属于实例对象
在程序运行时,类同样会被加载到内存,类对象只有一份,使用一个类可以创建出多个对象实例。除了封装实例的属性和方法外,类对象还可以拥有自己的属性和方法,即类属性和类方法,通过**类名.**的方式可以访问类的属性或调用类的方法。
(2)类属性和实例属性
类属性就是给类对象中定义的属性,通常用来记录与这个类相关的特征,不会用于记录具体对象的特征。
class Tool(object):
# 定义类属性,记录创建工具对象的总数
count = 0
def __init__(self, name):
self.name = name
# 针对类属性做一个计数
Tool.count += 1
tool1 = Tool("斧头")
tool2 = Tool("榔头")
tool3 = Tool("锤子")
# 类属性的获取机制:1.类名.类属性 2.对象.类属性(向上查找机制)
print(Tool.count) # 3
print(tool1.count) # 3
注意:如果使用 对象.类属性 = 值 赋值语句,只会给对象添加一个实例属性,而不会影响到类属性的值。
(3)类方法
类方法就是针对类对象定义的方法,在类方法内部可以直接访问类属性或调用其他类方法。
# 语法:
# 类方法需要用修饰器 @classmethod 来标识
# 类方法的第一个参数是cls,和实例方法的第一个参数self类似
# 通过类名.调用类方法,调用方法时,不需要传递cls参数
@classmethod
def 类方法名(cls):
# 在方法内部可以通过cls.访问类属性或其他类方法
print("工具对象总数为 %d" % cls.count)
pass
# 通过类名.调用类方法
Tool.类方法名()
(4)静态方法
在开发中,如果需要在类中封装一个方法,这个方法既不需要访问实例属性或调用实例方法,也不需要访问类属性或调用类方法,则可以把这个方法封装为静态方法。
# 语法:
# 静态方法需要用修饰器 @staticmethod 来标识
# 通过 类名. 调用静态方法
@staticmethod
def 静态方法名():
pass
(5)总结
实例方法 – 方法内部需要访问实例属性
类方法 – 方法内部需要访问类属性
静态方法 – 方法内部不需要访问实例属性和类属性
若在方法内部既要访问类属性又要访问实例属性,那应该定义成什么方法?
定义成实例方法,因为类只有一个,在实例方法内部可以使用类名.访问类属性。