注意:
1、类的私有属性、私有方法,在类的外部,对象(也就是类的实例)是无法访问的;如果要访问,可以在类的内部,再定义一个方法,让它去访问私有属性和私有方法,然后在类的外部,通过调用这个方法,就能轻松访问到类的私有属性和私有方法了。
2、当形参有缺省值时(也叫默认值),不传实参,那么形参就用默认值。
如果要传实参:
(1)当实参个数 < 形参个数时,如果实参的值不是传给第一个形参,那么实参在写的时候就要写上对应的形参名。因为默认会传给第一个,然后依次往下排。
(2)当实参个数 = 形参个数时,不用写形参名。
3、普通方法能调用类属性、类方法,而类方法不能调用普通方法和普通属性。
4、无论是在类里的方法嵌套,还是一般程序中的函数嵌套,如果有两个函数:第一个函数中有return;第二个函数是直接调用第一个函数,此时,执行第二个函数,结果是控制台上啥都没有。例如:
def func1():
a = 1
b = 2
return a + b
def func2():
func1()
func2() # 调用函数2、运行函数2
结果:
进程已结束,退出代码 0
你可能会想,func1里带return,那我加个print就好了,结果:却返回的是None。答案还是不对。
print(func2())
结果:
None
我们想要的是让它返回3,但是却是None,说明,还是没有拿到函数1的返回值。所以需要把函数2进行改进,把它改成return func1(),这样再运行就是我们想要的结果了。
def func1():
a = 1
b = 2
return a + b
def func2():
return func1() # 返回调用函数1的结果
print(func2())
结果:
3
小结:当发生函数嵌套时,如果被调用的函数是带return,那么另个函数在调用的时候,前面一定要加return。
5、如果类中定义的属性是个常量,那怎么修改这个属性的值?为了看的更清楚,这里将对:类属性和普通属性分别进行演示。
首先是对类属性的值进行修改:4种方法。
class Dog:
name = "大黄" # 类属性
#法1
Dog.name = "二哈" # 类名.类属性名直接修改为“二哈”
print(Dog.name) #输出修改后的类属性值
结果:
二哈
#法2:通过类方法中的形参来进行修改
class Dog:
name = "大黄" # 类属性
@classmethod # 类方法
def set_name(cls, name):
cls.name = name # 通过形参来修改类属性name的值
print(cls.name) # 输出修改后类属性的值
Dog.set_name("二哈")
结果:
二哈
# 法3 在对象访问时进行修改
class Dog:
name = "大黄" # 类属性
d = Dog() # 创建类的实例
d.name = "二哈" # 用对象去修改类属性的值
print(d.name) # 输出修改后的值
结果:
二哈
# 法4 通过普通方法中的形参来进行修改
class Dog:
name = "大黄" # 类属性
def demo(self, a): # a为demo的形参
Dog.name = a # 形参值赋给类属性。注意:普通方法中调用类属性:类名.类属性名
print(Dog.name)
d = Dog()
d.demo("二哈") # 调用普通方法,实参为二哈
结果:
二哈
小结:修改类属性的值,总的来说就两种方式:要么在类的内部进行修改,要么在类的外部进行修改。如果是在类的内部,那么类方法和普通方法都能调用类属性,所以从这就能衍生出来两种方法。而如果是在类的外部,那么又有两种访问方式:类名.类属性名 或者 对象名.类属性名,这样又能衍生出来两种方法,所以一共四种。
对普通属性的值进行修改:一种方法。即:直接通过对象名.属性名=值的方式进行修改也就是法1。下面的法2 其实就是把属性定义成变量,而不是对常量进行修改。要是定义成变量,那就有两种方式,例如下面的法2和法3 。
# 法1 :在对象中进行修改————推荐
class Dog:
def __init__(self):
self.age = 5 # 普通属性,这是个常量
d = Dog() # 类的实例
d.age = 10 # 修改属性的值
print(d.age) # 输出属性的值
结果:
10
#法2本质上就是把属性设为变量。
# 法2 通过方法的形参来修改——不推荐,这种的话,init初始化方法就没必要写了
class Dog:
def __init__(self):
self.age = 5 # 普通属性为常量
def set_age(self, age): # 普通方法,age为形参
self.age = age # 将形参age的值赋给属性name
print(self.age) # 其实这种写法本质上,也是定义属性name,与上面的init一样
d = Dog()
d.set_age(10) # 调用方法,10为实参
结果:
10
# 改进——去掉多余的init方法
class Dog:
def set_age(self, age): # 普通方法,age为形参
self.age = age # 将形参age的值赋给属性name
print(self.age) # 其实这种写法本质上,也就是定义属性name
d = Dog()
d.set_age(10) # 调用方法,10为实参
结果:
10
# 法3——把属性定义成变量
class Dog:
def __init__(self, age):
self.age = age
def __str__(self): # str方法返回的必须是字符串类型,所以这里把age的int改为str
return str(self.age)
d = Dog(10)
print(d)
结果:
10
6、静态方法不能访问类里面的其它成员,但类里面的其它成员却可以访问它。
一、封装
封装就是把类的属性和方法通过两个下划线封装到类的内部,完了只能在类的内部使用它,不能在类的外部使用。这就叫封装。
也就是说,把类的属性和方法前面加俩下划线,让他们变成类是私有属性和私有方法,完了只能在类的内部使用,不能在类的外部使用,这就叫封装。
具体做法:在属性名或方法名前面加两个下划线,这个属性和方法就变成了类的私有属性和私有方法。此时,它俩就只能在类的内部访问和调用,在类的外部就不能用了。
私有属性:就是类不希望公开的属性。
私有方法:就是类不希望公开的方法。
例如:定义一个User类,它有一个非私有属性name和一个非私有方法shou_name,以及一个私有属性密码和一个私有方法__show_passwd。
# 访问类的非私有属性
class User:
def __init__(self):
self.name = "小花" # 把属性name设置为常量
self.__passwd = "123" # __passwd是一个私有属性
def show_name(self): # 显示名字
print(self.name)
def __show_passwd(self): # 定义一个私有方法
print(self.__passwd)
a = User()
a.show_name()
结果:
小花
# 此时想要访问它的私有属性——密码,如果还用之前的对象名.方法名的方式去访问,会发现报错,无法访问
class User:
def __init__(self):
self.name = "小花" # 把属性name设置为常量
self.__passwd = "123" # __passwd是一个私有属性
def show_name(self): # 显示名字
print(self.name)
def __show_passwd(self): # 定义一个私有方法
print(self.__passwd)
a = User()
a.__show_passwd()
结果:
a.__show_passwd()
AttributeError: 'User' object has no attribute '__show_passwd'
那怎么访问类的私有属性和私有方法呢?很简单,我们可以在类的内部定义一个方法,让它专门用来访问类的私有属性和私有方法,然后在类的外部再调用这个方法即可。
例如:定义一个demo方法,让它用来访问私有属性和私有方法。
class User:
def __init__(self):
self.name = "小花" # 把属性name设置为常量
self.__passwd = "123" # __passwd是一个私有属性
def show_name(self): # 显示名字
print(self.name)
def __show_passwd(self): # 定义一个私有方法
print(self.__passwd)
def demo(self): # 定义一个方法,用来访问类的私有属性和私有方法
print(self.__passwd) # 访问类的私有属性
self.__show_passwd() # 调用类的私有方法
a = User()
a.show_name()
a.demo()
结果:
小花
123
123
观察上面代码可以看到,两个属性用的都是常量,这样的结果是,无论哪个对象调用都是显示,小花和123,因此,为了增加代码的灵活性,现在把它改为变量。即:在初始化方法中加形参。
例如:把name、passwd这两个属性写成变量。
class User:
def __init__(self, name="小花", passwd="123"):
self.name = name # 把属性name设置为【变量】
self.__passwd = passwd # 把__passwd也设置为【变量】
def show_name(self):
print(self.name)
def __show_passwd(self): # 定义一个私有方法
print(self.__passwd)
def demo(self): # 定义一个方法,用来访问类的私有属性和私有方法
print(self.__passwd) # 访问类的私有属性
self.__show_passwd() # 调用类的私有方法
a = User() # 实例化时候括号里面没加实参,那么,名字和密码都将使用默认值。
a.show_name()
a.demo()
结果:
小花
123
123
# 实例化时候,括号里加实参,此时,名字和密码将用实参的值
b = User("张三", "aasd")
b.show_name()
b.demo()
结果:
张三
aasd
aasd
当然,实例化时候,实参的个数也可以不写全。
例如:分别只传一个实参。
class User:
def __init__(self, name="小花", passwd="123"):
self.name = name # 把属性name设置为【变量】
self.__passwd = passwd # 把__passwd也设置为【变量】
def show_name(self):
print(self.name)
def __show_passwd(self): # 定义一个私有方法
print(self.__passwd)
def demo(self): # 定义一个方法,用来访问类的私有属性和私有方法
print(self.__passwd) # 访问类的私有属性
self.__show_passwd() # 调用类的私有方法
b = User("李四")
b.show_name()
b.demo()
结果:
李四
123
123
b = User("YYDS") # 本来是要把YYDS传给密码,但是因为没写形参名,导致传给了name
b.show_name()
b.demo()
结果:
YYDS
123
123
# !!当有多个形参时,如果要传递的形参不是排第一个,
# 那么实参在写的时候要加上对应的形参名,否则,默认都将传给第一个。
b = User(passwd="YYDS") # 密码在形参中排第二个,所以实参在写的时候,一定要加上形参名
b.show_name()
b.demo()
结果:
小花
YYDS
YYDS
需要注意的是,当有多个形参时,要传递的实参,如果不是传给第一个形参,那么实参在写的时候一定要加上对应的形参名字,否则会默认把它传给第一个形参。例如,上面代码中,如果要把YYDS这个实参传递给密码,那么实参在写的时候,就要写成,passwd="YYDS",否则就会传给第一个形参name了。当然,如果实参个数与形参个数一致,那么就不用写形参名了。
b = User("张三", "YYDS")
b.show_name()
b.demo()
结果:
张三
YYDS
YYDS
二、继承
主要用来实现代码的复用,相同的代码不需要重复的去编写。
语法:
class 父类:
pass
class 类名(父类名):
pass
例如:
class Animal:
pass
class Dog(Animal): # 狗继承自动物
pass
解析:子类也叫派生类、父类也叫基类、继承也叫派生。
例如上面这段代码:狗是动物的子类,又可以说,狗是动物的派生类。
动物是狗的父类,又可以说,动物是狗的基类。
狗继承于动物,狗派生于动物。
注意:
(1)子类继承父类之后,子类就拥有了父类所有的方法和属性(除了父类的私有属性、私有方法,无法继承)。
例如:Dog类继承自Animal类,此时,Dog类就有三种方法。
class Animal:
def sleep(self):
print("睡觉")
def eat(self):
print("吃东西")
class Dog(Animal): # Dog类继承自Animal类,此时,Dog类就有三种方法
def run(self):
print("跑")
d = Dog()
d.sleep()
d.eat()
d.run()
结果:
睡觉
吃东西
跑
(2)父类中所有的私有属性、私有方法归父类所有,子类不能继承。能继承的都是公有属性和公有方法。
class Animal:
def __init__(self, name="豆豆"):
self.__name = name # 父类私有属性
def __eat(self): # 私有方法不会被子类继承
print("%s在吃东西" % self.__name)
class Dog(Animal):
pass
d = Dog()
d.__eat()
结果:
d.__eat()
AttributeError: 'Dog' object has no attribute '__eat'
那怎么访问父类的私有属性和私有方法呢?可以通过父类的公有方法来间接的访问到父类私有属性和私有方法。
class Animal:
def __init__(self, name="豆豆"):
self.__name = name
def __eat(self): # 私有方法不会被子类继承
print("%s在吃东西" % self.__name)
def demo(self): # 利用demo这个公有方法,来间接的访问私有方法和私有属性
self.__eat() # 访问私有方法
class Dog(Animal):
pass
d = Dog()
d.demo() #调用demo
结果:
豆豆在吃东西
(3)所谓继承,一定是在有可继承关系的两个事物之间。比如,动物和狗。而没有关系的两个事物之间是不存在继承的,比如,植物和狗。这俩都没啥联系,也就没办法继承。
(4)继承是单向的,子类能拥有父类的属性和方法,而父类却不能拥有子类的属性和方法。也就是说,父类里不能访问(调用)子类中特有的属性和方法。
例如:父类访问子类中的run方法,结果报错。
class Animal: # 父类Animal
def eat(self):
print("吃")
class Dog(Animal): # 子类1
def run(self):
print("能跑能跳")
a = Animal() # 为父类创建一个对象
a.run() #父类调用子类的方法——报错,说明,继承是单向的
结果:
a.run()
AttributeError: 'Animal' object has no attribute 'run'
(5)一个父类可以有多个子类。每个子类也可以拥有自己特有的属性和方法。
例如:Animal这个父类,可以有三个子类,而这三个子类中,继承父类方法的同时,又各自拥有自己的其它方法。
class Animal: # 父类Animal
def eat(self):
print("吃")
class Dog(Animal): # 子类1
def run(self):
print("能跑能跳")
class Bird(Animal): # 子类2
def fly(self):
print("能飞")
class Fish(Animal): # 子类3
def swimming(self):
print("能河里游")
d = Dog() # 创建一个对象
d.eat()
d.run() # 狗这个子类,所特有的方法
b = Bird() # 创建一个对象
b.eat()
b.fly() # 鸟这个子类,所特有的方法
f = Fish() # 创建一个对象
f.eat()
f.swimming() # 鱼这个子类,所特有的方法
结果:
吃
能跑能跳
吃
能飞
吃
能河里游
(6)继承的传递性——继承支持多级的继承
例如,类C继承自类B,而类B继承自类A,那么类C,就同时拥有了类A和类B里所有属性和方法。
例如:柯基继承于狗,狗继承于动物,此时,柯基就拥有了狗和动物所有的方法。
class Dog(Animal): # Dog继承于Animal
def run(self):
print("能跑能跳")
class Keji(Dog): # Keji继承于Dog
def play(self):
print("会闹腾")
k = Keji()
k.eat() # 调用父类的父类的方法
k.run() # 调用父类的方法
k.play() # 调用自己的方法
结果:
吃
能跑能跳
会闹腾
再比如,给父类加个属性。
class Animal:
def __init__(self, name="动物", color="橘黄色"):
self.name = name
self.color = color
def eat(self):
print("%s的%s在吃东西" % (self.color, self.name))
class Dog(Animal): # Dog继承于Animal
def run(self):
print("能跑能跳")
class Keji(Dog): # Keji继承于Dog
def play(self):
print("%s的%s会闹腾" % (self.color, self.name)) # 调用Animal的属性
k = Keji()
k.eat() # 调用父类的父类的方法
k.run() # 调用父类的方法
k.play() # 调用自己的方法
结果:
橘黄色的动物在吃东西
能跑能跳
橘黄色的动物会闹腾
1、继承之后,方法的重写
所谓方法的重写就是说,当父类的方法不能满足子类的需求时,子类可以对父类的方法进行重写。
有两种方式可以实现重写:覆盖父类的方法、扩展父类的方法。
两种方法的异同:
相同点:都是在子类中定义一个和父类同名的方法。
不同点:覆盖是直接不要父类的方法了。而扩展是在父类方法的基础上又增加了新功能。
下面分别介绍:
(1)覆盖父类的方法。
当父类的方法完全不能满足子类需求时,可以在子类中,定义一个和父类同名的方法,以此来覆盖父类的方法。这样在运行的时候,调用的就是子类中重写的方法了。
例如:覆盖父类的方法。
class Animal:
def eat(self): # 父类中定义了吃
print("吃")
class Dog(Animal): # 子类中也定义了吃
def eat(self): # 子类中出现了和父类同名的方法,那么父类的eat就会被覆盖
print("啃骨头")
d = Dog()
d.eat() # 因为父类的eat方法被覆盖了,所以这里调用的是子类的eat方法
结果:
啃骨头
(2)扩展父类的方法。
当父类方法不能完全满足子类需求时,可以在子类中,定义一个和父类同名的方法,然后在需要的位置上,使用super().同名方法,来调用父类方法。其它位置上再编写自己想要的功能,这样就能在父类方法的基础上增加新的功能。
例如:扩展父类方法。
class Animal:
def eat(self):
print("吃")
class Dog(Animal):
def eat(self):
print("啃骨头")
super().eat() # 在子类方法中,调用父类的eat方法
d = Dog()
d.eat() # 既执行了父类的eat,又增加了功能
结果:
啃骨头
吃
注意:无论是哪种方式的重写,都是要在子类中定义一个和父类同名的方法,否则就不叫重写,最终调用的依然是父类的方法,而子类的方法不会被调用。
例如:想要重写父类的eat方法,但是子类中却写了个at方法,结果,不会执行子类的方法。
class Animal:
def eat(self):
print("吃")
class Dog(Animal):
def at(self):
super().eat()
print("啃骨头")
d = Dog()
d.eat() # 因为子类里没有eat方法,所以这里调用的是父类的eat方法
结果:
吃
但是,如果想要调用子类的方法怎么办?直接,对象.子类方法名即可。
class Animal:
def eat(self):
print("吃")
class Dog(Animal):
def at(self):
super().eat() # 这样虽然也能运行,但是这就不叫重写了,因为子类跟父类都不同名。
print("啃骨头") #这里准确的来说应该叫,方法的嵌套
d = Dog() #即:子类et方法中,调用了父类eat方法
d.at() #类的内部调用方法,正确的写法是self.方法名
#所以super().eat() 应该改为self.eat()
结果:
吃
啃骨头
class Animal:
def eat(self):
print("吃")
class Dog(Animal):
def at(self):
self.eat() # 在子类方法中,调用了父类的eat方法
print("啃骨头")
d = Dog()
d.at()
结果:
吃
啃骨头
三、多态
不同的子类对象,调用相同的父类方法,产生不同的结果,这就是多态。
所谓子类对象,就是对子类创建实例。
多态的前提,不同的子类,来自于同一个父类,子类要覆盖父类的方法(也就是说,子类要重写父类方法)。
例如:狗和牛这两个子类,都调用父类的eat方法,结果却不一样。
class Animal: # 父类
def food(self): # 父类方法
pass
def eat(self): # 父类方法
self.food()
class Dog(Animal): # 定义子类,名叫Dog——狗
def food(self): # 重写父类方法
print("狗啃骨头")
class Cow(Animal): # 定义子类,名叫Cow——牛
def food(self): # 重写父类方法
print("牛吃草")
d = Dog() # 为Dog创建一个实例
d.eat() # 子类调用父类方法
c = Cow() # 为Cow创建一个实例
c.eat() # 子类调用父类方法
结果:
狗啃骨头
牛吃草
四、类属性、类方法
先梳理下普通属性、普通方法的定义和调用。
普通属性的定义和调用:
定义:使用self关键字定义在init初始化方法中。
调用:(1)在类的内部:self.属性名
(2)在类的外部:先创建类的实例,然后通过对象名.属性名来调用。通常是要结合print函数,因为,对象名.属性名,只是说你拿到了这个值,但是你没输出,控制台就啥都没有,所以,一般都是要写成:print(对象名.属性名)。
普通方法的定义和调用:
定义:直接def 方法名即可,方法的第一个形参是self。
调用:(1)在类的内部:self.方法名( )
(2)在类的外部:先创建类的实例,然后通过对象名.方法名( )来调用。注意:如果方法中有return关键字,那就写成:print(对象名.方法名( ))。
1、类属性
定义:直接定义在类里面,即:属性名=值。
调用:(1)在类的内部:如果在普通方法中:类名.类属性名;如果在类方法中,cls.类属性名。
(2)在类的外部:类名.类属性名。通常是要结合print函数,因为,类名.类属性名,只是说你拿到了这个值,但是你没输出,控制台就啥都没有,所以,一般都是要写成:print(类名.类属性名)。当然,可以通过对象来调用。先创建一个对象,然后被对象调用,print(对象名.类属性名)。
小结:也就是说类属性在定义的时候,不用init方法,不用self关键字。调用的时候,如果是自己调,就直接,类名.类属性名。如果被对象调用,就是print(对象名.类属性名)。
2、类方法
定义:先用@classmethod修饰,然后def 方法名即可,方法的第一个形参是cls。
调用:(1)在类的内部:如果在普通方法中:类名.类方法名( );如果在类方法中,cls.类方法名( )
(2)在类的外部:自己调自己就是,类名.类方法名( )。 注意,如果方法中有return就写成:print(类名.类方法名( ))。如果被对象调用,就是,先创建一个对象,然后,print(对象名.类方法名())来被对象调用。
小结:类方法中,只能访问类属性和类方法,不能访问普通属性和普通方法。因为类方法不需要创建对象,直接通过类名就能调用了。而普通方法可以访问类方法和类属性。在普通方法中,直接通过类名.类属性名或者类名.类方法名( )进行访问。
3、类属性、类方法的使用场景
常用来计数。
例如:判断类被实例化了几次。
class Dog:
index = 0 # 定义一个类属性
@classmethod # 定义一个类方法,名字叫count
def count(cls):
return cls.index
def __init__(self):
Dog.index += 1 # 调用类属性:【类名.类属性名】
d = Dog()
print(Dog.count())
结果:
1
d1 = Dog()
d2 = Dog()
d3 = Dog()
print(Dog.count())
结果:
3
4、表格式总结类属性、类方法的定义和调用
例如:现在有一个name属性和一个show_name方法,分别把它们定义成普通属性、普通方法和类属性、类方法。
|
定义 |
在类的内部调用 |
在类的外部调用 |
普通属性 |
class Person: def __init__(self,name=”张三”): self.name=name |
self.name 即:【self.属性名】 |
p1=Person() print(p1.name) 即:先创建对象,再使用【对象名.属性名】的方式调用。 |
普通方法 |
def show_name(self): print(self.name) |
self.show_name() 即:【self.方法名()】 |
p1=Person() p1.show_name() 即:先创建对象,再【对象名.方法名】 注意:普通方法可以访问类属性类方法。 例如:访问类属性 print(类名.类属性名) Person.类方法名() #调用类方法 |
类属性 |
class Person: name=”张三” |
如果被类方法调用:【cls.类属性名】 如果被普通方法调用:【类名.类属性名】 |
两种方式: (1)【类名.类属性名】 (2)【对象名.类属性名】 即: print(Person.name)或 print(p1.name) |
类方法 |
@classmethod def show_name(cls): print(cls.name) |
如果被类方法调用: 【cls.类方法名()】 如果被普通方法调用: 【类名.类方法名()】 |
两种方式: (1)【类名.类方法名()】 (2)【对象名.类方法名()】 即:Person.show_name()或【p1.show_name()】 注意:类方法只能访问类属性、类方法 |
代码展示:
例如:把name属性和show_name方法,定义成普通属性和普通方法。
class Person:
def __init__(self, name="张三"): # 定义一个普通属性
self.name = name
def show_name(self): # 定义一个普通方法
print(self.name) # 普通方法调用普通属性:【self.普通属性名】
p1 = Person() # 创建对象、为类Person创建一个实例
print(p1.name) # 对象调用类的普通属性 【对象名.普通属性名】
p1.show_name() # 对象调用类的普通方法:【对象名.普通方法名】
结果:
张三
张三
把name属性和show_name方法,定义成类属性和类方法。
class Person:
name = "张三" # 定义一个类属性
@classmethod # 定义一个类方法:(1)要用@classmethod修饰。
def show_name(cls): # (2)方法的第一个形参是cls。
print(cls.name) # 调用类属性:【类名.类属性名】
print(Person.name) # 调用类属性:【类名.类属性名】
Person.show_name() # 调用类方法:【类名.类方法名】
结果:
张三
张三
那二者能互相调用吗?普通方法能调用类属性、类方法。但类方法不能调用普通属性和普通方法。
~~~前方高能!!!~~~~
5、【重要!!!】例题演示,类属性、类方法、普通属性、普通方法之间的定义和调用问题。
例如:下面这个代码就清楚的说明了,上面表格中的怎么定义、怎么在内部外部进行调用的问题。
class Dog:
name = "大黄" # 类属性
@classmethod # 类方法
def get_name(cls):
return cls.name # 调用类属性:【类名.属性名】
@classmethod # 类方法
def demo1(cls): # 方法的嵌套
return cls.get_name() # 调用类方法:【cls.方法名()】
def __init__(self):
self.age = 10 # 普通属性
def get_age(self): # 普通方法
return self.age # 调用普通属性:【self.属性名】
def demo2(self): # 普通方法
print(Dog.name) # 调用类属性:【类名.属性名】
return Dog.get_name() # 调用类方法:【类名.方法名()】
# 第一种情况:先分别调用自己
print(Dog.name) # 访问类属性:【类名.类属性名】
print(Dog.get_name()) # 访问类方法:【类名.类方法名()】
d = Dog()
print(d.age) # 访问普通属性:【对象名.属性名】
print(d.get_age()) # 访问普通方法:【对象名.方法名()】
结果:
大黄
大黄
10
10
# 第二种情况:普通方法访问:类属性、类方法
d = Dog()
print(d.demo2()) # 法1:把类属性、类方法都写demo里
结果:
大黄
大黄
#法2:直接用对象调用,不写demo2里
d = Dog()
print(d.name) # 【对象名.类属性名】
print(d.get_name()) # 【对象名.类方法名()】
结果:
大黄
大黄
下面两个代码也是在说明,普通方法都能调用类属性、类方法,而类方法不能调用普通方法和普通属性,这个问题,尽管以重名为例,但本质是一样的。可以跳过直接看第五节、静态方法。
注意:普通方法和类方法一定不能重名,重名的话会报错。例如下面代码:普通方法和类方法重名了,此时,谁写在下面谁就会覆盖上面的,这里就是,普通方法覆盖了类方法,此时要再调用类方法,结果会报错,因为类方法被覆盖了,所以它就不在了,此时再去调用方法名显然调用的是普通方法里的方法。而普通方法的调用方法是,先创建对象,然后通过对象名.方法名才能调用。
class Person:
name = "张三" # 类属性
@classmethod # 类方法:
def show_name(cls):
print(cls.name) # 调用类属性
def show_name(self): # 普通方法和类方法重名
print("hello")
Person.show_name() # 调用类方法
结果:
Person.show_name()
TypeError: show_name() missing 1 required positional argument: 'self'
#因为普通方法写在类方法下面,所以普通方法会覆盖类方法
#此时再想调用show_name就只能调用的普通方法里的
#所以需要把Person.show_name()改掉
p1 = Person()
p1.show_name()
结果:
hello
反之,如果普通方法写在上面而类方法写在下面,那么此时,普通方法就被覆盖了,后面再调用的话,只能调用类方法。
class Person:
name = "张三" # 类属性
def show_name(self):
print("hello")
@classmethod # 类方法:
def show_name(cls):
print(cls.name) # 调用类属性
p1 = Person()
p1.show_name() # 普通方法被覆盖了,所以调用的是同名的类方法
结果:
张三
#或者直接写成Person.show_name(),结果也是一样。
Person.show_name()
结果:
张三
也就是说:当类方法和普通方法重名时,谁在下面谁有效,因为上面的会被覆盖,所以上面的就无效了。即:
(1)当普通方法有效时,只能通过【对象名.方法名】的方式调用。
(2)当类方法有效时,可以通过【对象名.方法名】的方式调用,也可以通过【类名.方法名】的方式调用。
说白了就是,无论重不重名,普通方法都能调用类属性、类方法,而类方法不能调用普通方法和普通属性。
五、静态方法
1、定义
在类中用@staticmethod修饰的方法就叫静态方法。静态方法不能访问类中所有的成员(也就是不能访问所有的属性和方法),它只是一个独立存在于类中的函数。但是类中的方法却可以访问它。
说白了,就是把一个函数放到类里面去,然后用@staticmethod进行修饰,这样的方法就叫静态方法,虽然在类里,但是它不能访问别人,而别人却可以访问它,它自己只是单纯的存在于类中而已。
2、调用
(1)在类的外部调用:类名.静态方法名()
(2)在类的内部调用:self.静态方法名()
例如:
class A:
@staticmethod # 定义一个静态方法test
def test():
print("静态方法1")
@classmethod
def demo2(cls):
cls.test() # 类方法调用静态方法
def demo1(self):
self.test() # 普通方法调用静态方法
A.demo2() # 调用类方法:【类名.类方法名】
a = A() # 创建类A的实例
a.demo1() # 调用普通方法:【对象名.普通方法名】
结果:
静态方法1
静态方法1
3、使用场景
当一个项目特别大的时候,代码量就会特别大,用到的函数也就会特别多,为了避免由于函数重名而产生的错误,这时候,就可以把同名的函数放到不同的类里面去,让它作为静态方法,然后再去调用它,这样就不会出错了。
例如:有两个同名的test函数,功能分别是:
def test():
print("静态方法1")
def test():
print("静态方法2")
为了让这两个函数都有效,都不被覆盖,就可以使用静态方法。把它们分别放到两个类里面去,然后再分别用@staticmethod进行修饰即可。
例如:把它们分别放到类A和类B里面去。
class A:
@staticmethod
def test():
print("静态方法1")
class B:
@staticmethod
def test():
print("静态方法2")
A.test()
B.test()
结果:
静态方法1
静态方法2
可以看到,俩test方法都能成功调用了。
当然,也可以不用静态方法,直接把它们分别放到两个类里面去也可以,只是在调用的时候会比较麻烦,需要创建对象然后再通过对象名.方法名调用。
例如:把上面的代码进行改进,不用静态方法。
class A:
def test(self): # 定义一个方法
print("静态方法1")
class B:
def test(self): # 定义一个方法
print("静态方法2")
a = A() # 为类A创建实例
a.test() # 调用A里的test方法
b = B() # 为类B创建实例
b.test() # 调用类B里的test方法
结果:
静态方法1
静态方法2
结果也是一样的,但是显然,用静态方法使用起来会更方便些,不用创建对象了。
六、object类
在python3中,如果一个类定义的时候,没写父类,那就默认父类就是object,它是Python自带的。而python2中,如果没写,那就代表没有父类,不会像python3这样,会自动继承object。
所以python3中写类的时候,object写不写都行,不写就代表默认object,写了也不算错误。
例如:定义一个Dog类,两种写法都对,但推荐1 。
#写法1—————— 推荐
class Dog:
pass
#写法2
class Dog(object):
pass