一、创建对象
__init__是类的一个特殊的方法,每当根据类创建新实例时,Python都会自动运行它。这就是一个初始化手段,Python中的__init__方法用于初始化类的实例对象。__init__方法的作用一定程度上与C++的构造函数相似,但并不等于。C++的构造函数是使用该函数去创建一个类的示例对象,而Python执行__init__方法时实例对象已被构造出来。__init__方法会在对象构造出来后自动执行,所以可以用于初始化所需要的数据属性。
>>>class Cat():
... """再次模拟猫咪的简单尝试"""
... # 构造器方法
... def __init__(self,name,age):
... self.name = name # 属性
... self.age = age
... def sleep(self):
... """模拟猫咪被命令睡觉"""
... print('%d岁的%s正在沙发上睡懒觉。'%(self.age, self.name))
... def eat(self,food):
... """模拟猫咪被命令吃东西"""
... self.food = food
... print('%d岁的%s在吃%s'%(self.age, self.name,self.food))
把属性name和age放入了__init__方法中进行初始化,通过实参向Cat类传递名字和年龄。self会自动传递,因此创建对象时只需给出后两个形参(name和age)的值即可。
二、删除对象
创建对象时,默认调用构造方法。当删除一个对象时,同样也会默认调用一个方法,这个方法为析构方法。__del__是类的另一个特殊的方法,当使用del删除对象时,会调用它本身的析构函数,另外,当对象在某个作用域中调用完毕,在跳出其作用域的同时,析构函数也会被调用一次,释放内存空间。
>>>class Animal():
... # 构造方法
... def __init__(self):
... print( '---构造方法被调用---')
... # 析构方法
... def __del__(self):
... print( '---析构方法被调用---')
>>>cat = Animal()
---构造方法被调用---
>>>print(cat)
<__main__.Animal object at 0x0000000009851400>
>>>del cat
---析构方法被调用---
>>>print(cat)
Traceback (most recent call last):
File "<ipython-input-43-7c3364e51d98>", line 1, in <module>
print(cat)
NameError: name 'cat' is not defined
三、掌握对象的属性和方法
学习了类的定义和方法后,可以尝试建立具体的对象来深一层的学习面向对象程序设计。以刚才构造的猫咪为例,创建实例对象测试代码。
>>>class Cat():
... def __init__(self,name,age):
... self.name = name
... self.age = age
... def sleep(self):
... print('%d岁的%s正在沙发上睡懒觉。'%(self.age, self.name))
... def eat(self,food):
... self.food = food
... print('%d岁的%s在吃%s。'%(self.age, self.name,self.food))
>>>cat1 = Cat('Tom', 3) # 创建对象
>>>cat2 = Cat('Jack',4)
>>>print('Cat1的名字为:',cat1.name) # 访问对象的属性
Cat1的名字为: Tom
>>>print('Cat2的名字为:',cat2.name)
Cat2的名字为: Jack
>>>print(cat1.sleep()) # 访问对象的方法
3岁的Tom正在沙发上睡懒觉。
>>>print(cat2.eat('fish'))
4岁的Jack在吃fish。
创建对象和调用一个函数很相似,使用类名作为关键字创建一个类的对象。这时候创建实例对象就需要参数了,实际上这是__init__函数的参数。__init__自动将数据属性进行了初始化,然后调用相关函数能够返回需要的对象的数据属性。
1. 对象的属性
对象的变量:由类的每个实例对象拥有。因此每个对象有自己对这个域的一份拷贝,即它们不是共享的,在同一个类的不同实例中,即使对象的变量有相同的名称,也互不相关。通俗的说法就是不同的对象调用该变量,其变量值改变后互不影响。
对于类属性和对象属性,如果在类方法中引用某个属性,该属性必定是类属性,而如果在实例对象方法中引用某个属性(不作更改),并且存在同名的类属性,此时若实例对象有该名称的对象属性,则对象属性会屏蔽类属性,即引用的是对象属性,若实例对象没有该名称的对象属性,则引用的是类属性;如果在实例对象方法更改某个属性,并且存在同名的类属性,此时若实例对象有该名称的对象属性,则修改的是对象属性,若实例对象没有该名称的实例属性,则会创建一个同名称的对象属性。想要修改类属性,如果在类外,可以通过类对象修改,如果在类里面,只有在类方法中进行修改。
2. 对象的方法
(1) 方法引用
类的方法和对象的方法是一样的。在定义类的方法时程序没有为类的方法分配内存,而在创建具体实例对象的程序才会为对象的每个数据属性和方法分配内存。定义类的方法是def定义的,具体定义格式与普通函数相似,只是类的方法的第一个参数需要是self参数。用普通函数实现对对象函数的引用。
>>>cat1 = Cat('Tom', 3)
>>>sleep = cat1.sleep
>>>print(sleep())
3岁的Tom正在沙发上睡懒觉。
>>>cat2 = Cat('Jack',4)
>>>eat = cat2.eat
>>>print(eat('fish'))
4岁的Jack在吃fish。
虽然看上去似乎是调用了一个普通函数,但是sleep函数和eat函数是引用cat1.sleep()和cat2.eat()的,这意味着程序还是隐性地加入了self参数。
(2) 私有化
如果要获取对象的数据属性并不需要通过sleep、eat等方法,直接在程序外部调用数据属性即可。
>>>print(cat1.age)
3
>>>print(cat2.name)
Jack
尽管这似乎很方便,但是却违反了类的封装原则。对象的状态对于类外部应该是不可访问的。查看Python模块代码时会发现源码里面定义的很多类,模块中的算法通过使用类实现是很常见的,如果使用算法时能够随意访问对象中的数据属性,那么很可能在不经意中修改了算法中已经设置的参数,这是很麻烦的。一般封装好的类都会有足够的函数接口供程序员使用,程序员没有必要访问对象的具体数据属性。
为防止程序员无意中修改对象的状态,需要对类的数据属性和方法进行私有化。Python不支持直接私有方式,但可以使用一些小技巧达到私有特性的目的。为了让方法的数据属性或方法变为私有,只需要在它的名字前面加上双下划线即可,修改前文创建的Car类代码。
>>>class Cat():
... def __init__(self,name,age):
... self.__name = name
... self.__age = age
... def sleep(self):
... """模拟猫咪被命令睡觉"""
... print('%d岁的%s正在沙发上睡懒觉。'%(self.__age, self.__name))
... def eat(self,food):
... """模拟猫咪被命令吃东西"""
... self.__food = food
... print('%d岁的%s在吃%s。'%(self.__age, self.__name,self.__food))
... def getAttribute(self):
... return self.__name,self.__age
>>>cat1 = Cat('Tom', 3) # 创建对象
>>>cat2 = Cat('Jack',4)
>>>print('Cat1的名字为:',cat1.name) # 从外部访问对象的属性,会发现访问不了
Traceback (most recent call last):
File "<ipython-input-57-c85f1f024f81>", line 1, in <module>
print('Cat1的名字为:',cat1.name)
AttributeError: 'Cat' object has no attribute 'name'
>>>print('Cat2的名字为:',cat2.name)
Traceback (most recent call last):
File "<ipython-input-58-526f8a44b064>", line 1, in <module>
print('Cat2的名字为:',cat2.name)
AttributeError: 'Cat' object has no attribute 'name'
>>>print(cat1.sleep()) # 只能通过设置好的接口函数来访问对象
3岁的Tom正在沙发上睡懒觉。
>>>print(cat2.eat('fish'))
4岁的Jack在吃fish。
>>>print(cat1.getAttribute())
('Tom', 3)
现在在程序外部直接访问私有数据属性是不允许的,只能通过设定好的接口函数去调取对象的信息。不过通过双下划线实现的私有化实际上是“伪私有化”,实际上还是可以做到从外部访问这些私有数据属性。
>>>print(cat1._Cat__name)
Tom
>>>print(cat1._Cat__age)
3
Python使用的是一种name_mangling技术,将__membername替换成_class_membername,在外部使用原来的私有成员时,会提示无法找到,而执行cat1._Cat__name是可以访问。简而言之,确保其他人无法访问对象的方法和数据属性是不可能的,但使用这种name_mangling技术是一个程序员不应该从外部访问这些私有成员的强有力信号。
四、练习
创建一个Car类,赋予它车轮数(4),颜色(red)的属性,并定义函数实现输出“汽车有4个车轮,颜色是红色。”及“车行驶在学习的大道上。”,再进行调用类的方法(函数)。
参考代码:
class Car():
# 构造器方法
def __init__(self, newWheelNum, newColor):
self.wheelNum = newWheelNum
self.color = newColor
# 方法(函数)
def run(self):
print ('车在跑,目标:夏威夷。')
# 析构方法
def __del__(self):
print( '---析构方法被调用---')
BMW = Car(4, 'green') # 创建对象
print( '车的颜色为:',BMW.color) # 访问属性
print( '车轮子数量为:',BMW.wheelNum)
BMW.run() # 调用对象的run方法
del BMW # 删除对象
print(BMW) # 查看是否删除
Python面向对象编程系列文章两周一更!
文章未经博主同意,禁止转载!