类
类是抽象的模板,实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。
通过类定义数据类型的属性(数据)和方法(行为),属性用来描述数据,方法(即函数)用来描述这些数据相关的操作。也就是说,“类将行为和状态打包在一起”。
python中一切皆对象,类也称为“类对象”,类的实例也称为“实例对象”。
一个类创建实例对象时,每个对象会共享这个类的行为(类中定义的方法),但会有自己的属性值(不共享状态)。更具体一点:“方法代码是共享的,属性数据不共享”。
类的定义:
class 类名:
类体
class Person:
count = 0 #类属性
#构造器
def __init__(self,name,age):
self.name = name #实例属性
self.age = age
Person.count += 1
#实例方法
def say(self):
print('我叫{0},今年{1}岁。'.format(self.name,self.age))
# 创建实例对象
p1 = Person('张三',12)
p1.say()
p2 = Person('李四',18)
p2.say()
print('共创建了{0}个对象'.format(Person.count))
一个 Python 对象包含如下部分:
1. id(identity 识别码)
2. type(对象类型)
3. value(对象的值)
(1) 属性(attribute)
(2) 方法(method)
创建实例对象,我们需要定义构造函数__init__()方法。构造方法用于执行“实例对象的初始化工作”,即对象创建后,初始化当前对象的相关属性,无返回值,如上示例中初始化实例对象的name与age属性。如果我们不定义__init__方法,系统会提供一个默认的__init__方法。如果我们定义了带参的__init__方法,系统不创建默认的__init__方法。通过“类名(参数列表)”来调用构造函数。调用后,将创建好的对象返回给相应的变量。
self相当于java中的this,指代当前创建的实例对象。名字任意,一般为self。
实例
实例属性
从属于实例对象的属性。一般在__init__方法中定义,self.实例属性名 = 初始值。
实例方法
从属于实例对象的方法。
def 方法名(self [, 形参列表]):
函数体
定义时第一个参数必须是self,调用方法时,不需要给self传参。self由解释器自动传参。
class Person:
def __init__(a,name,age):
a.name = name #实例属性
a.age = age
def say(a):
print('我叫{0},今年{1}岁。'.format(a.name,a.age))
p1 = Person('张三',12)
#对象调用方法
p1.say()
#解释器翻译成下面形式,自动传参,结果一样
Person.say(p1)
函数与方法,本质都是完成特定功能的语句块,区别在于,直观上看,方法定义需要传递self,函数不需要;方法通过对象来调用,函数没有此特点;
其他操作:
1. dir(obj)可以获得对象的所有属性、方法
2. obj.__dict__ 对象的属性字典
3. pass 空语句
4. isinstance(对象,类型) 判断“对象”是不是“指定类型”
类对象
class Person:
pass
print(type(Person)) #<class 'type'>
print(id(Person)) #类对象的地址
从上面示例中可以看出,构建的Person类是type类的实例,称为类对象。
类属性
类属性是从属于“类对象”的属性,也称为“类变量”,可以被所有实例对象共享。
类属性的定义方式:
class 类名:
类变量名= 初始值
类属性通过类名.类变量名进行访问。
类方法
类方法是从属于“类对象”的方法。用于操作类属性。类方法通过装饰器@classmethod 来定义,格式如下:
@classmethod
def 类方法名(cls [,形参列表]) :
函数体
class Person:
count = 0 #类属性
@classmethod
def printCount(cls): #cls 代表类本身
print(cls.count)
Person.printCount()
调用类方法格式:“类名.类方法名(参数列表)”。 参数列表中,不需要也不能给 cls 传值。
类方法中不能访问实例属性和实例方法。
子类继承父类方法时,传入 cls 是子类对象,而非父类对象。
静态方法
“静态方法”和在模块中定义普通函数没有区别,只不过“静态方法”放到了“类的名字空间里面”,需要通过“类调用”。
静态方法通过装饰器@staticmethod 来定义,格式如下:
@staticmethod
def 静态方法名([形参列表]) :
函数体
class Person:
count = 0
def __init__(self,name,age):
self.name = name
self.age = age
#类方法
@classmethod
def printCount(cls): #cls 代表类本身
print(cls.count)
# print(self.name) #不能访问实例属性
#静态方法
@staticmethod
def staticSum(a,b):
print('{0}与{1}的和是:{2}'.format(a,b,a+b))
# print(self.name) #不能访问实例属性
Person.printCount()
Person.staticSum(1,2)
静态方法格式:“类名.静态方法名(参数列表)”。
静态方法中也不能访问实例属性和实例方法。
__del__方法(析构函数)和垃圾回收机制
__del__方法称为“析构方法”,用于实现对象被销毁时所需的操作。如释放对象占用的资源,包括打开的文件资源、网络连接等。
Python 自动的进行垃圾回收,当对象没有被引用时(引用计数为 0),由垃圾回收器调用__del__方法。
我们也可以通过 del 语句删除对象,从而保证调用__del__方法。
系统会自动提供__del__方法,一般不需要自定义析构方法。
class Person:
def __del__(self):
print('释放资源')
print('销毁实例对象{0}'.format(self))
p1 = Person()
del p1
__call__方法和可调用对象
定义了__call__方法的对象,称为“可调用对象”,即该对象可以像函数一样被调用。
class Person:
def __call__(self, name):
print('姓名:{0}'.format(name))
return dict(name=name)
p1 = Person()
a = p1('张三')
print(a)
方法没有重载
java中具有重载机制,方法名相同,参数列表不同。
python中没有重载。因为方法的参数没有声明类型(调用方法时传入确定的参数类型),参数的数量也可以由可变参数控制。
如果在类体中定义了重名的方法,只有最后一个有效!
class Person:
def say(self,name):
print('我的姓名是:{0}'.format(name))
def say(self,name,age):
print('我的姓名是:{0},年龄是:{1}'.format(name,age))
p1 = Person()
# p1.say('张三') # 报错:say() missing 1 required positional argument: 'age'
p1.say('李四',12)
方法的动态性
Python 是动态语言,可以动态的为类添加新的方法,或者动态的修改类的已有的方法。
class Person:
def work(self):
print('工作中,请勿打扰!')
def work2(self):#必须带self,指定当前对象
print('加油工作')
def play(s):
print('休息中...')
p1 = Person()
p1.work() #工作中,请勿打扰!
#对象不能直接调用类外方法
p1.work2() # 'Person' object has no attribute 'work2'
Person.work = work2
p1.work() #加油工作
Person.game = play
p1.game() #休息中...
可见,Person类动态新增了一个geme方法,以及用work2替换了work方法。
私有属性和私有方法(实现封装)
Python 对于类的成员没有严格的访问控制限制,这与java有明显区别。
方法本质上也是属性!只不过是可以通过()执行而已。所以,此处讲的私有属性和公有属性,也同时讲解了私有方法和公有方法的用法。
1. 通常约定,两个下划线开头的属性是私有的(private),其他为公共的(public)。
2. 类内部可以访问私有属性(方法)
3. 类外部不能直接访问私有属性(方法)
4. 类外部可以通过“_类名__私有属性(方法)名”访问私有属性(方法)
class Person:
__country = 'China' #私有的类属性,dir()可以查到为 _Person__country
def __init__(self,name,age):
self.__name = name #私有的实例属性
self.__age = age
def __work(self): #私有方法
print('work hard!')
def say(self):
print('国籍:',self.__country) #访问私有类属性
print('姓名:{0},年龄:{1}'.format(self.__name,self.__age)) #访问私有实例属性
p = Person('张三',12)
# 类外不能直接访问私有属性
# print(Person.country) # type object 'Person' has no attribute 'country'
# print(p.name)#'Person' object has no attribute 'name'
p.say() #私有属性在类内部可以访问
# 通过 对象._类名__私有属性(方法) 调用
print(p._Person__country)
print(p._Person__name)
p._Person__work()
print(dir(p)) #['_Person__age', '_Person__country', '_Person__name', '_Person__work', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'say']
可以看出,私有属性“__name”在实际存储时是按照“_Person__name”这个属性来存储的。
@Property 与@私有属性.setter 装饰器实现getter和setter
在java中,对私有属性的赋值与获取采用getter与setter方法,python中可以使用装饰器(注解)实现同样功能。
@property 可以将一个方法的调用方式变成“属性调用”。
class Person:
def __init__(self,name,age):
self.__name = name
self.__age = age #私有属性
# java中的getter与setter方法
'''
def getAge(self):
return self.__age
def setAge(self,age):
self.__age = age
'''
# 使用装饰器
@property
def age(self): #为了调用方便,方法名可与私有属性保持一致
return self.__age
@age.setter # 被property装饰的方法名.setter
def age(self,age): # 为了调用方便,方法名可与私有属性保持一致
if age>0:
self.__age = age
p = Person('张三',12)
# p.setAge(18)
# print(p.getAge())
print(p.age) # 直接获取私有属性
p.age = 20 # 直接设置私有属性
print(p.age)
属性和方法的命名总结:
_xxx:保护成员,不能用“from module import * ”导入,只有类对象和子类对象能访问这些成员
· __xxx__:系统定义的特殊成员
· __xxx: 类中的私有成员,只有类对象自己能访问,子类对象也不能访问。在类外部可以通过“对象名. _类名__xxx”这种特殊方式访问。Python 不存在严格意义的私有成员。