无论是类属性还是类方法,都无法像普通变量或者函数那样在类的外部直接使用它们
将类看做一个独立空间,类属性就是在类体中定义的变量类方法是在类体中定义的函数
方法与函数的最大的区别就是
方法必须通过类或者类的实例对象来调用 不可以单独调用而函数可以单独调用
在类体中根据变量定义的位置不同,以及定义的方式不同,类属性又可细分为:
类体中、所有函数之外:此范围定义的变量,称为类属性或类变量;
类体中,所有函数内部:以“self.变量名”定义的变量,称为实例属性或实例变量;
类体中,所有函数内部:以“变量名=变量值”定义的变量,称为局部变量。
类方法也可细分为实例方法、静态方法和类方法:
用 @classmethod 修饰的方法为类方法;
用 @staticmethod 修饰的方法为静态方法;
不用任何修改的方法为实例方法。
一、类中的属性/变量
(1)类变量:所有类的实例对象同时共享类变量,类变量是所有实例化对象的公用资源
通过类名claaName.variableName直接访问/修改类变量
通过类名修改类变量的值,会影响所有的实例化对象
通过类的实例对象调用/访问类属性(不推荐)
通过类对象只能访问不能修改类变量
这是因为通过类对象修改类变量的值(通过类对象对类变量赋值)不是在给“类变量赋值”,而是定义新的实例变量
类中实例变量和类变量可以同名,但这种情况下使用类对象将无法调用类变量,它会首选实例变量,这也是不推荐“类变量使用类的实例对象调用”的原因
class CLanguage :
# 下面定义了2个类变量
name = "C语言中文网"
add = "http://c.biancheng.net"
# 下面定义了一个say实例方法
def say(self, content):
print(content)
#使用类名直接调用
print(CLanguage.name)
print(CLanguage.add)
#使用类名修改类变量的值
CLanguage.name = "Python教程"
CLanguage.add = "http://c.biancheng.net/python"
print(CLanguage.name)
print(CLanguage.add)
#使用类名为类和类对象动态添加类变量
clang = CLanguage()
CLanguage.catalog = 13
print(clang.catalog)
#使用类实例对象访问类变量 不推荐
clang1 = CLanguage()
print(clang1.name)
print(clang1.add)
class CLanguage :
# 下面定义了2个类变量
name = "C语言中文网"
add = "http://c.biancheng.net"
# 下面定义了一个say实例方法
def say(self, content):
print(content)
clang2 = CLanguage()
#类对象clang2访问类变量
print(clang2.name)
print(clang2.add)
clang2.name = "Python教程"
clang2.add = "http://c.biancheng.net/python"
#clang2实例变量的值 改变了 相当于定义了clang对象的name add实例变量 这两个变量和类变量同名
#此时同名通过类对象只能访问到实例变量
print(clang2.name)
print(clang2.add)
#类变量的值 没改变
print(CLanguage.name)
print(CLanguage.add)
运行结果
C语言中文网
http://c.biancheng.net
Python教程
http://c.biancheng.net/python
C语言中文网
http://c.biancheng.net
(2)实例变量:只作用于调用方法的对象。只能通过对象名访问,无法通过类名访问
和类变量不同的是通过某个对象修改实例变量的值,不会影响类的其它实例化对象,更不会影响同名的类变量
Python 只支持为特定的对象添加实例变量
class CLanguage :
def __init__(self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
# 下面定义了一个say实例方法
def say(self):
self.catalog = 13
clang = CLanguage()
print(clang.name)
print(clang.add)
#由于 clang 对象未调用 say() 方法,因此其没有 catalog 变量,下面这行代码会报错
#print(clang.catalog)
clang2 = CLanguage()
print(clang2.name)
print(clang2.add)
#只有调用 say(),才会拥有 catalog 实例变量
clang2.say()
print(clang2.catalog)
此 CLanguage 类中,name、add 以及 catalog 都是实例变量
其中,由于 __init__() 函数在创建类对象时会自动调用
而 say() 方法需要类对象手动调用
因此,CLanguage 类的所有类对象都会包含 name 和 add 实例变量
而只有调用了 say() 方法的类对象才包含 catalog 实例变量
class CLanguage :
name = "xxx" #类变量
add = "http://" #类变量
def __init__(self):
self.name = "C语言中文网" #实例变量
self.add = "http://c.biancheng.net" #实例变量
# 下面定义了一个say实例方法
def say(self):
self.catalog = 13 #实例变量
clang = CLanguage()
#修改 clang 对象的实例变量
clang.name = "python教程"
clang.add = "http://c.biancheng.net/python"
print(clang.name)
print(clang.add)
clang.money=30
print(clang.money)
clang2 = CLanguage()
print(clang2.name)
print(clang2.add)
#输出类变量的值
print(CLanguage.name)
print(CLanguage.add)
运行结果
python教程
http://c.biancheng.net/python
30
C语言中文网
http://c.biancheng.net
xxx
http://
(3)局部变量:定义局部变量是为了所在类方法功能的实现。局部变量只能用于所在函数中,函数执行完成后,局部变量也会被销毁
二、类中的方法
类初始化方法/构造方法__init__(self)
在类的实例对象创建时就会执行初始化方法 初始化方法没有返回值 或者return None
初始化方法中self.variableName=value 表明实例初始化时就具有了variableName属性
并且属性的值为value 就相当于C++中类属性的声明与初始化
可以l通过dir(className)查看
注意init前后都是是两个英文下划线 否则报错
PythonException has occurred: TypeError ClassName() takes no arguments
(1)实例方法/普通方法
最少也要包含一个 self 参数,用于绑定调用此方法的实例对象(Python 自动绑定)
通常会用类对象直接调用 不需要给 self 参数传值
也支持使用类名调用实例方法,但此方式需要手动给 self 参数传值
self就是类对象
class CLanguage:
#类构造方法,也属于实例方法
def __init__(self):
self.name = "C语言中文网"
self.add = "http://c.biancheng.net"
def __init__(self, name):
self.name = name
self.add = "http://c.biancheng.net"
# 下面定义了一个say实例方法
def say(self):
print("正在调用 say() 实例方法")
# 下面定义了一个info类方法
@classmethod
def info(cls):
print("正在调用类方法",cls)
# 下面定义了一个info静态方法
@staticmethod
def info(name,add):
print(name,add)
clang = CLanguage() 这句之后dir(CLanguage) 就能看到name和add两个属性
clang.say()
clang = CLanguage('wjf') 这句之后dir(CLanguage) 就能看到name和add两个属性
clang.say()
#类名调用实例方法,需手动给 self 参数传值
clang = CLanguage()
CLanguage.say(clang)
(2)类方法
最少要包含一个参数 cls,Python 会自动将类本身绑定给 cls 参数(不是绑定类对象)因此在调用类方法时,无需显式为 cls 参数传参
类方法推荐使用类名直接调用,也可以使用实例对象来调用(不推荐)
cls就是类本身
#使用类名直接调用类方法 推进
CLanguage.info()
#使用类对象调用类方法
clang = CLanguage()
clang.info()
(3)静态方法
静态方法,其实就是我们学过的函数,和函数唯一的区别是静态方法定义在类这个空间(类命名空间)中,而函数则定义在程序所在的空间(全局命名空间)中
静态方法没有类似 self、cls 这样的特殊参数,因此 Python 解释器不会对它包含的参数做任何类或对象的绑定。也正因为如此,类的静态方法中无法调用任何类属性和类方法
静态方法的调用,既可以使用类名也可以使用类对象 推荐使用类名调用
在开发中,常常需要定义一些跟类有关的方法,但这些方法在实现时并不需要引用类或者实例,例如,设置环境变量,修改另一个类的变量等。这个时候可以使用静态方法
#使用类名直接调用静态方法
CLanguage.info("C语言中文网","http://c.biancheng.net")
#使用类对象调用静态方法
clang = CLanguage()
clang.info("Python教程","http://c.biancheng.net/python")
(4)为类动态地添加方法
Python 可为单个实例对象添加属性(实例属性)
也可为所有的类实例对象统一添加属性(类属性)
同样允许为类动态地添加上述三种方法
但对于实例对象,只允许动态地添加实例方法,不能添加类方法和静态方法
为单个实例对象添加方法,不会影响该类的其它实例对象
而如果为类动态地添加方法,则所有的实例对象都可以使用
class CLanguage:
pass
#下面定义了一个实例方法
def info(self):
print("正在调用实例方法")
#下面定义了一个类方法
@classmethod
def info2(cls):
print("正在调用类方法")
#下面定义个静态方法
@staticmethod
def info3():
print("正在调用静态方法")
#类可以动态添加以上 3 种方法,会影响所有实例对象
CLanguage.info = info
CLanguage.info2 = info2
CLanguage.info3 = info3
clang = CLanguage()
#如今,clang 具有以上 3 种方法
clang.info() 类对象调用实例方法 不用指定self
clang.info2() 类方法不管类对象调用还是类名调用都不用指定cls
clang.info3() 静态方法啥都不用指定
#类实例对象只能动态添加实例方法,不会影响其它实例对象
clang1 = CLanguage()
clang1.info = info
#必须手动为 self 传值
clang1.info(clang1)
注意正常情况下类的实例对象调用类的实例方法是不需要哦手动指定参数self的
但是如果是为类对象动态添加的实例方法 必须要手动指定参数self
区别于通过类名动态添加实例方法 不需要指定self
Python 提供__slots__ 属性来避免用户频繁的给实例对象动态地添加属性或方法。
_slots_ 只能限制为实例对象动态添加属性和方法,无法限制动态为类添加属性和方法
__slots__传送门(http://c.biancheng.net/view/2291.html)
三、实例解析
class Date(object):
def __init__(self,year=0,month=0,day=0):
self.year=year
self.month=month
self.day=day
@classmethod
def from_string(cls,date_as_string):
year,month,day=map(int,date_as_string.split('-'))
date1=cls(year,month,day)
return date1
@staticmethod
def is_date_valid(date_as_string):
year,month,day=map(int,date_as_string.split('-'))
return day<=31 and month<=12 and year<=2200
d=Date.from_string('2020-11-11')
is_date=Date.is_date_valid('2020-11-11')
print(is_date)
最后两行代码通过类名调用类方法和静态方法
map函数中的第一个参数int是一个内置函数对象 将数字字符串转成整型
上面代码中date1=cls(year,month,day) 等价于 date1=Date(year,month,day)
cls就是类本身Date 这行代码创建类的实例date1 因此会去调用构造方法
_init_(self,year=0,month=0,day=0) 其中的默认值参数被覆盖
因为我们需要除了采用年月日来创建实例以外 还希望通过输入格式为‘xx-xx-xx’的字符串来创建实例 因此定义一个from_string类方法
与实例无关的方法定义为静态方法
不管定义什么样的实例我们都要用该方法(字符串截取)取判断是否合法 与日期实例无关
其实上述代码我们也可以按照如下方法来实现
class Date(object):
def __init__(self,year=0,month=0,day=0):
self.year=year
self.month=month
self.day=day
def __init__(self,date_as_string):
self.year,self.month,self.day=map(int,date_as_string.split('-'))
#@classmethod
#def from_string(cls,date_as_string):
# year,month,day=map(int,date_as_string.split('-'))
# date1=cls(year,month,day)
# return date1
@staticmethod
def is_date_valid(date_as_string):
year,month,day=map(int,date_as_string.split('-'))
return day<=31 and month<=12 and year<=2200
d0=Date('2020-11-11')
#d=Date.from_string('2020-11-11')
is_date=Date.is_date_valid('2020-11-11')
print(is_date)
但是有一个问题 此时from_string和第一个init不能存在 否则报错
原因在于python不像c++那样 支持同名函数 函数名相同但是参数不同(类型或个数时)的重载
我们写了两个init,37行的init会覆盖掉32行的init(即使是在同一个类中) 因此43行代码报错
Exception has occurred: TypeError
__init__() takes 2 positional arguments but 4 were given
因此这段代码最终的有效代码如下
class Date(object):
def __init__(self,date_as_string):
self.year,self.month,self.day=map(int,date_as_string.split('-'))
@staticmethod
def is_date_valid(date_as_string):
year,month,day=map(int,date_as_string.split('-'))
return day<=31 and month<=12 and year<=2200
d0=Date('2020-11-11')
is_date=Date.is_date_valid('2020-11-11')
这并不满足我们需要除了采用年月日来创建实例以外
还希望通过输入格式为‘xx-xx-xx'的字符串来创建实例的需求
故而采用类方法还是有必要的
关于python对齐的问题 tab键和四个空格不能混用 否则报错
IndentationError: unindent does not match any outer indentation level
总结
C++中
重载:函数名相同,函数的参数类型或者个数不同
重写:派生类中函数名、参数类型和个数必须与基类中的函数完全一样
隐藏:派生类中的函数名与基类中的函数名相同(不管参数同不同)
(1)重载:函数名相同,函数的参数类型或者个数不同,注意与函数的返回值类型无关,同一个类中仅仅返回值不一样的两个函数不是重载,重载必须在同一个类中
(2)重写与覆盖意义相同:子类重新定义父类中有相同名称和参数的虚函数,重写的函数必须要有virtual关键字修饰(父类和子类中都需要该关键字),否则不能称之为重写或者覆盖。
只有在继承和派生的时候才可能出现,重写(覆盖)是指在派生类中存在重新定义的函数,其函数名、参数类型和数量必须与基类中的函数保持一致,只有函数体不同(即实现方式不同)。
(3)隐藏:隐藏也是只有在继承或者派生中才可能出现的情况,隐藏是指派生类的函数屏蔽了其基类的函数。注意只要派生类中的函数名与其基类中的函数名相同(不管参数同不同),基类函数将会被屏蔽。注意隐藏方法无法实现多态性。
C++重载 重写 隐藏传送门
python 中:同名只看函数名 不看参数
同一个类中不支持函数重载 后写的同名函数会覆盖之前写的
父类与子类之间同名函数支持重写