动态与静态方法
普通方法
只有实例化对象之后才可以使用的方法,该方法的第一个形参接收的一定是对象本身!如果使用类名.函数
名调用普通方法会报错。
class Cat:
'''定义了一个Cat类'''
# 方法
def eat(self):
print("猫在吃鱼....")
静态方法
静态方法是类中的函数,不需要实例
主要用来存放逻辑性的代码,与类本身没有交互,即在静态方法中不会涉及到类内的方法和属性若想调用类
内的方法和属性则可通过(__class__.类属性)
形式:添加@staticmethod装饰器
调用:类名.类方法名(或者对象名.类方法名)
参数:静态方法可以有参数也可以无参数
class Cat:
'''定义了一个Cat类'''
n = 1
# 方法
@staticmethod
def eat():
print(__class__.n)
print("猫在吃鱼....")
Cat.eat()
# 创建一个对象
tom = Cat()
tom.eat()
动态方法
绑定给对象的方法
class Cat:
'''定义了一个Cat类'''
# 方法
def drink(self):
print("猫正在喝kele.....")
tom = Cat()
tom.drink() 对象调用绑定给对象的方法:会自动将对象当做第一个参数传入
'''当类调用绑定给对象的方法时需要传入参数,如果没参数会报错。'''
Cat.drink()
''' TypeError: Cat.drink() missing 1 required positional argument: 'self' '''
Cat.drink(1)
猫正在喝kele.....
绑定给类的方法(类方法)
无需实例化,可以通过类直接调用的方法,但是方法的第一个参数接收的一定是类本身
(1)在方法上面添加@classmethod
(2)方法的参数为 cls 也可以是其他名称,但是一般默认为cls
(3)cls 指向 类对象
(5)应用场景:当一个方法中只涉及到静态属性的时候可以使用类方法(类方法用来修改类属性)。
(5)使用 可以是 对象名.类方法名 或者是 类名.类方法名
class Cat:
'''定义了一个Cat类'''
name = '小猫咪'
@classmethod
def drink(cls):
print(cls.name)
print("猫正在喝kele.....")
类调用绑定给类的方法:会自动将类当做第一个参数传入
Cat.drink()
小猫咪
猫正在喝kele.....
# 创建一个对象
tom = Cat()
对象调用绑定给类的方法:也不需要传参 会讲产生该对象的类自动当做第一个参数传入
tom.drink()
小猫咪
猫正在喝kele.....
面向对象的三大特征
封装
封装的概念:就是将复杂的信息、流程给包起来,内部处理,让使用者只需要通过简单的操作步骤,就能实
现。
继承
基本概念:在面向对象的世界中,从父类继承,就可以直接拥有父类的属性和方法,这样可以减少代码、多
复用。子类可以定义自己的属性和方法
基本使用:
"""
class A(B):
pass
我们将被继承的类称为:父类或者基类 B
继承别人的类称为:子类或者派生类 A
"""
如果类定义时,没有继承别的类,等同于继承自object。在Python3中,object类是所有对象的根基
类
class A:
pass
# 等价于
class A(object):
pass
在python中一个类可以同时继承多个父类
class A(B,C,D):
pass
继承中__init__()方法
__init__方法的作用是初始化类中的属性。当你创建实例的时候,类中的__init__方法会自动执行。不
需要你显式的调用。
'''
class A(object):
def __init__(self):
print('._init_方法已执行')
example1 = A()
._init_方法已执行
'''
注意:__init__()方法中第一个形参必须是 self。 形参self做作用很大。每个与类相关联的方法
调用都自动传递实参self,它是一个指向实例本身的引用,让实例能够访问类中的属性和方法。
子类没有定义自己的初始化函数,父类的初始化函数会被默认调用
class parent():
def __init__(self,race,country):
self.race = race
self.country = country
def information(self):
print('我来自{},是{}人'.format(self.country,self.race))
def test(self):
print('父类的方法')
class son(parent):
pass
example1 = son('中国','浙江')
example1.information()
我来自浙江,是中国人
'''
tip1:此时需要注意,当子类实例化时,由于子类没有初始化,此时父类的初始化函数就会默认被调用,
且必须传入父类的参数。
如果不传入父类的参数就会报错
'''
子类定义了自己的初始化函数,而在子类中没有显式调用父类的初始化函数,则父类的属性不会被初始化
class parent():
def __init__(self,race,country):
self.race = race
self.country = country
def information(self):
print('我来自{},是{}人'.format(self.country,self.race))
def test(self):
print('父类的方法')
class son(parent):
def __init__(self):
pass
example1 = son()
example1.test()
父类的方法
'''tip1:此时用子类创建实例的时候,就按照子类的初始化函数传入实参。如果还按照父类的就会报错'''
'''tip2: 父类的属性没有被初始化,无法通过子类创建的实例来直接访问父类中的属性'''
'''tip3: 此时还是可以访问父类中的方法的'''
如果子类定义了自己的初始化函数,显式调用父类,子类和父类的属性都会被初始化
class parent():
def __init__(self,race,country):
self.race = race
self.country = country
def information(self):
print('我来自{},是{}人'.format(self.country,self.race))
def test(self):
print('父类的方法')
class son(parent):
def __init__(self, race, country,name):
super().__init__(race,country)
# super(son, self).__init__(race,country)
pass
example1 = son('中国','浙江',111)
example1.information()
我来自浙江,是中国人
'''tip1:此时子类中初始化函数中参数必须包括父类中的参数,也就是父类初始化函数的形参是子类初始化函数中形参的子集,否则会报错'''
'''即super().__init__(*arg1)中的*arg1 必须是 __init__(self,*args2) 的*args2的子集'''
''' *arg1 ∈ *args2 '''
三种情况的总结
1、子类中若有__init__()方法,则创建实例时传入的参数,须与__init__()方法中形参数量一致(self不算在内)
2、这三种情况,父类中的方法都是可以调用的
3、使用子类创建实例的时候,父类的初始化方法必须被子类显式调用才会执行。而子类的初始化方法会自动执行
单继承
名字的查找顺序是
先从对象自己的名称空间中查找 没有则去产生对象的类中查找
如果还没有并且类有父类则去父类中查找 以此往复下去!!!
对象 >>> 类 >>> 父类
经典案例
class A:
def f1(self):
print('from A.f1')
def f2(self):
print('from A.f2')
self.f1() '''以后看到self点东西 一定要问自己self是谁'''
class MyClass(A):
def f1(self):
print('from MyClass.f1')
obj = MyClass()
obj.f2()
执行结果:
from A.f2
from MyClass.f1
多继承(了解)
#父类
class Person: # 默认直接继承自obj
def eat(self):
print("人吃饭!")
def play(self):
print("happy!")
class Man: # 默认直接继承自obj
def eat(self):
print("男人吃饭!")
def drink(self):
print('男人抽烟')
# 子类
class Children(Person,Man):
def eat(self):
print("孩子吃饭!")
pass
#创建对象
ch=Children()
ch.eat()
ch.drink()
# (<class '__main__.Children'>, <class '__main__.Person'>, <class '__main__.Man'>, <class 'object'>)
print(Children.__mro__)
'''
如果方法同名,首先优先调用自己的,如果自己没有,那么就按照继承关系,从左至右一个个寻找;
继承关系 Children > Person > Man > object
'''
'''
非菱形继承的情况下
父类中名字的查找顺序就是按照继承时从左往右依次查找
如果多个父类还有分类 那么遵循"深度优先"
ADBECF
菱形继承的情况下
父类中名字的查找顺序就是按照继承时从左往右依次查找
如果多个父类还有分类 那么遵循"广度优先"
ADBECFM
'''
#############################################
'''名字的查找顺序永远都是 先从当前对象自身开始查找'''
#############################################
方法重写覆盖
基类(被继承的类)的成员都会被派生类(继承的新类)继承,当基类中的某个方法不完全适用于派生类时,就
需要在派生类中重写父类的这个方法。
基类中定义的harvest()方法,无论派生类是什么水果都显示"水果…",如果想要针对不同水果给出不同的
提示,可以在派生类中重写harvest()方法。例如,在创建派生类Orange()时,重写harvest()方法如下:
class Fruit:
color = '绿色'
def harvest(self, color):
print(f"水果是:{color}的!")
print("水果已经收获...")
print(f"水果原来是{Fruit.color}的!")
class Apple(Fruit):
color = "红色"
def __init__(self):
print("我是苹果")
class Orange(Fruit):
color = "橙色"
def __init__(self):
print("\n我是橘子")
def harvest(self, color): # 重写harvest
print(f"橘子是:{color}的!")
print("橘子已经收获...")
print(f"橘子原来是{Fruit.color}的!")
apple = Apple() # 实例化Apple()类
apple.harvest(apple.color) # 在Apple()中调用harvest方法,并将Apple()的color变量传入
orange = Orange()
orange.harvest(orange.color) # 在Orange()中调用harvest方法,并将Orange()的color变量传入
执行结果:
我是苹果
水果是:红色的!
水果已经收获...
水果原来是绿色的!
我是橘子
橘子是:橙色的!
橘子已经收获...
橘子原来是绿色的!
'''
如子类中和父类同时存在这个方法名称,将只会执行子类中的这个方法,不会调用父类的同名方法(包括__init__())
'''
派生类
派生就是子类在继承父类的基础上衍生出新的属性。子类中独有的,父类中没有的;或子类定义与父类
重名的东西。子类也叫派生类。
class Animal:
def run(self):
print('小动物喜欢成天跑个不停')
class Cat(Animal):
def eat(self):
print('猫喜欢吃老鼠')
tom = Cat()
tom.run()
# 多出来的行为
tom.eat()
# 多出来的属性,对象的属性是可以随时添加的,派生
tom.color = '蓝色'
print(tom.color)
super关键字
我们举一个简单例子,我们父类是Human,有两个属性,分别是姓名和性别;然后定义一个子类Student。
class Human:
def __init__(self, name, sex):
self.name = name
self.sex = sex
print('parent')
class Student(Human):
pass
stu_1 = Student('lisi', 'male')
print(stu_1.name)
这里Student没有构造函数,所以会去父类中寻找构造函数。这时候我们需要在子类中加入构造函数,并需
要name,sex,score三个属性,那我们如果直接写就应该是下面这种代码。
class Human:
def __init__(self, name, sex):
self.name = name
self.sex = sex
print('parent')
class Student(Human):
def __init__(self, name, sex, score):
self.name = name
self.sex = sex
self.score = score
print('child')
stu_1 = Student('lisi', 'male', 97)
print(stu_1.score)
你会发现父类和子类都有相同的两行代码。
self.name = name
self.sex = sex
这显然和我们优雅的Python格格不入,所以super函数来了,我们直接看代码。
class Human:
def __init__(self, name, sex):
self.name = name
self.sex = sex
print('parent')
class Student(Human):
def __init__(self, name, sex, score):
super().__init__(name, sex)
self.score = score
print('child')
stu_1 = Student('lisi', 'male', 97)
print(stu_1.score)
通过代码我们可以看出,super函数常常用于子类的构造函数中,用于调用父类(超类)的构造函数,并且不
会显式引用基类。
此时子类中初始化函数中参数必须包括父类中的参数,也就是父类初始化函数的形参是子类初始化函数中
形参的子集,否则会报错
不仅仅是用于构造函数
super函数虽常用于构造函数,但是父类的其他函数一样也是可以用super函数的。
class A:
def add(self, x):
y = x + 1
print(y)
class B(A):
def add(self, x):
super().add(x)
b = B()
b.add(2)
# 3
之所以不常用,我认为是既然继承了父类,那子类就可以直接调用父类的方法,这样做只是多此一举。
class A:
def add(self, x):
y = x + 1
print(y)
class B(A):
pass
b = B()
b.add(2)
# 3
多重继承可能就不一样了
根据上面的案例,我们可以看出super函数是直接调用基类的构造函数,但是多重继承不一样,他是调用继
承顺序的下一个类,而不是父类。
class Base:
def __init__(self):
print("enter Base")
print("leave Base")
class A(Base):
def __init__(self):
print("enter A")
super().__init__()
print("leave A")
class B(Base):
def __init__(self):
print("enter B")
super().__init__()
print("leave B")
class C(A, B):
def __init__(self):
print("enter C")
super().__init__()
print("leave C")
C()
enter C
enter A
enter B
enter Base
leave Base
leave B
leave A
leave C
super函数和直接调用父类方法的区别
在单继承时,我们看到super和直接调用父类方法得到的结果是一样的,只是不会显式引用基类。但多重继
承就不一样了,我把上面的代码进行了修改,我相信你能看懂区别。
class Base:
def __init__(self):
print("enter Base")
print("leave Base")
class A(Base):
def __init__(self):
print("enter A")
Base.__init__(self)
print("leave A")
class B(Base):
def __init__(self):
print("enter B")
Base.__init__(self)
print("leave B")
class C(A, B):
def __init__(self):
print("enter C")
A.__init__(self)
B.__init__(self)
print("leave C")
C()
enter C
enter A
enter Base
leave Base
leave A
enter B
enter Base
leave Base
leave B
leave C