构造函数和析构函数
构造方法的使用
使用上面的方式可以创建对象,但是,很多类都倾向于将对象创建为有初始化状态.因此类可能定义一个名为__init__()的特殊方法(构造方法)
构造方法也叫做构造器,是指当实例化一个对象(创建一个对象)的时候,第一个被自动调用的方法.
演示1:构造方法被调动的动机
class Person():
name = ""
age = 0
#构造方法
def __init__(self):
print("构造函数被执行了")
#创建对象的过程中构造函数被自动调用
#得出结论:创建对象的过程中调用了构造函数
#当未手动添加构造函数时,系统会默认提供一个无参的构造函数
p1 = Person
演示2:构造函数和普通函数之间的区别和练习
说明:构造函数本质上还是一个函数,函数可以有参数,也可以无参,所以同样的道理,构造方法也是如此
class Person():
name = "stu"
age = 10
height = 160
weight = 90
def run(self):
print("run")
def eat(self, food):
print("eat "+ food)
#一般情况下,有构造方法的参数和成员变量有关,并且在设置的过程中与成员变量同名
def __init__(self, name, age, height, weight):
#print(nname, age, height, weight)
#因为构造方法是创建对象的过程中被调用的
#所以构造方法的作用一般是用来定义成员变量并且给成员变量赋值
#定义属性并给属性赋值
#通过self来进行区分是成员变量还是形参
self.name = name
self.age = age
self.height = height
self.weight = weight
'''
构造函数: __init__() 在使用类创建对象的时候自动调用
注意: 如果不显式的写出构造函数,默认会自动添加一个空的构造函数,函数体部分什么都不实现
'''
per = Person("hanmeimei", 20, 170, 55)
print(per.name, per.age)
per.run()
per2 = Person("lilei", 21, 175, 70)
print(per2.name, per2.gae)
析构函数的使用
析构函数: del 释放对象时自动调用
演示:
class Person():
def run(self):
print("run")
def eat(self, food):
print("eat "+food)
def __init__(self, name, age, height, weight):
self.name = name
self.age = age
self.height = height
self.weight = weight
def __del__(self):
print("这里是析构函数")
per = Person("hanmeimei", 20, 170, 55)
#释放对象
del per
#注意:对象释放后就不能再进行访问了
#在函数里定义的对象,会在函数结束时自动释放,这样可以用来减少内存空间的浪费
#其实就是作用域的问题
def func():
per2 = Person("aa", 1, 1, 1)
func()
self的使用
注意:self代表类的实例[对象],而非类本身
类的方法与普通的函数只有一个特殊的区别—>他们必须有一个额外的第一个参数名称,按照惯例它的名字是self
class Test():
def prt(self):
print(self)
print(self.__class__)
t = Test
t.prt()
演示2:self不是python的关键字
class Person():
def run(self):
print("run")
print(self.__class__)
#通过这种方式也可以进行对象的初始化
p = self.__class__("tt", 30, 10, 30)
def eat(self, food)
print("eat" + food)
def say(self)
print("hello, my name is %s, I am %d year old"%(self.name, self.age))
def play(a)
print("play",a.name)
def __init__(self, name, age, height, weight):
self.name = name
self.age = age
self.height = height
self.weight = weight
#self代表此时正在创建对象,则self.属性表示当前对象的属性
per1 = Person("tom", 20, 160, 80)
per1.say()
per2 = Person("henmeimei", 21, 160, 80)
per2.say()
per1.say()
per1.run()
总结:
1.self在定义的时候需要定义,但是在调用的时候会自动传入
2.self的名字并不是规定死的,但是最好还是按照约定使用self
3.self总是指调用时的类的实例
拓展
定义在类中的变量我们又分为成员变量以及类变量(或者称静态成员变量)
类变量定义在类中,且在函数体之外,类变量通常不作为实例变量使用,类变量在实例化的过程中是公用的。
成员变量:定义在方法中且通过self绑定在实例上的变量,只作用于当前实例。
class Student(object):
#类变量
classname = "1802"
def __init_(self):
#成员变量
self.name = "zhang"
#self.classname = "1888"
#变量的调用
stu = Student()
#成员变量的调用
print(stu.name)
#类变量的调用
print(stu.classname)
print(Student.classname)
#更改变量的值
#通过类名来更改类变量的值
Student.classname = "1804"
#通过对象更改类变量的值
stu.classname = "1803"
#创建一个新的变量
stu2 = Student()
print(stu2.classname)
#"1804"
print(Student.classname)
#"1804"
结论:
1.当类变量与成员变量同名时,使用对象来调用时,默认调用的是成员变量的值
2.当成员变量不存在,并且类变量存在的时候,在使用对象调用属性的时候,会调用类变量
3.成员变量只能通过对象来调用,而类变量不但可以通过对象调用,还可以通过类名来调用
4.通过对象来更改成员变量或者是类变量的值的时候,只是改变的当前对象的值,而通过类名来更改类变量的值的时候,更改的则是类变量的初始值。
成员方法:
''' 通常情况下,在类中定义的所有函数(注意了,这里说的就是所有,跟self啥的没关系,self也只是一个再普通不过的参数而已)都是对象的绑定方法,对象在调用绑定方法时会自动将自己作为参数传递给方法的第一个参数。除此之外还有两种常见的方法:静态方法和类方法,二者是为类量身定制的,但是实例非要使用,也不会报错。'''
class Student():
def say(self):
print("**********")
静态方法:
静态方法是一类特殊的方法,有时你可能需要写一个属于这个类的方法,但是这些代码完全不会使用到实例对象本身,比如:
'''
静态方法是一种普通函数,位于类定义的命名空间中,不会对任何实例类型进行操作,python为我们内置了函数@staticmethod来把类中的函数定义成静态方法
'''
class Student():
@staticmethod
def say(self):
print("**********")
@staticmethod
def spam(x,y,z): #类中的一个函数,千万不要懵逼,self和x啥的没有不同都是参数名
print(x,y,z)
Student.say()
Student.spam(1,23,3)
类方法:
什么是类方法呢?类方法不是绑定到对象上,而是绑定在类上的方法。
'''
类方法是给类用的,类在使用时会将类本身当做参数传给类方法的第一个参数,python为我们内置了函数@classmethod来把类中的函数定义成类方法
'''
class Test:
x=1
@classmethod
def test(cls):
print(cls,cls.x)
Test.test()
无论你用哪种方式访问这个方法,它总是绑定到了这个类身上,它的第一个参数是这个类本身
什么时候使用这种方法呢?类方法通常在以下两种场景是非常有用的:
'''1.工厂方法:它用于创建类的实例,例如一些预处理。如果使用@staticmethod代替,那我们不得不硬编码Pizza类名在函数中,这使得任何继承Pizza的类都不能使用我们这个工厂方法给它自己用。'''
class Pizza():
price = 20
def __init__(self,taste):
self.taste = taste
@classmethod
def getPrice(cls,size):
return size*cls.price
'''
2.调用静态类:如果你把一个静态方法拆分成多个静态方法,除非你使用类方法,否则你还是得硬编码类名。使用这种方式声明方法,Pizza类名永远都不会在被直接引用,继承和方法覆盖都可以完美的工作。
'''
class Pizza(object):
def __init__(self, radius, height):
self.radius = radius
self.height = height
@staticmethod
def compute_area(radius):
return math.pi * (radius ** 2)
@classmethod
def compute_volume(cls, height, radius):
return height * cls.compute_area(radius)
强调,注意注意注意:静态方法和类方法虽然是给类准备的,但是如果实例去用,也是可以用的,只不过实例去调用的时候容易让人混淆,不知道你要干啥
#结论:
区别,实例方法隐含的参数为类实例self,而类方法隐含的参数为类本身cls。 静态方法无隐含参数,主要为了类实例也可以直接调用静态方法。
所以逻辑上,类方法应当只被类调用,实例方法实例调用,静态方法两者都能调用。主要区别在于参数传递上的区别,实例方法悄悄传递的是self引用作为参数,而类方法悄悄传递的是cls引用作为参数。
访问限制
概念
面向对象语言的三大特征:封装, 继承, 多态
广义的封装: 类和函数定义本身就是封装的体现
狭义的封装:一个类的某些属性,不希望外界直接访问,而是把这个属性私有化[只有当前类持有],然后暴露给外界一个访问的方法即可.
封装的本质:就是属性私有化的过程
封装的好处:提供了数据的复用性,保证了数据的安全性
在class内部可以有属性和方法,而外部的代码可以通过直接调用实例变量的方法来操作数据,这样就隐藏了内部的复杂逻辑。
但是从我们之前定义的class来看,外部代码还是可以自由的修改一个实例的name等属性。
使用
如果要让内部的属性不被外部访问,可以把属性名前加两个下划线,在python中以双下划线开头的变量就变成了一个私有的变量,只有内部可以访问,而外部不能访问。
class Person(object):
def __init__(self, name, age, height, money):
self.name = name
self.__age__=age
self.__money = money
修改完毕之后,对于外部的代码几乎没有变动,但是已经无法从外部访问money变量了
>>>per = Person("hanmeimei", 20, 170, 10000)
>>>per.__money
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Student' object has no attribute '__money'
这样就确保了外部的代码不能随意的修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。
但是,如果外部的代码需要获取money的属性的时候该怎么办呢?
我们可以通过getMoney与setMoney的方法来操作数据
class Person(object):
def run(self):
print(self.__money)
def eat(self):
print("eat",food)
def __init__(self, name, age, height, weight, money):
self.name = name
self.__age__=age
self.weight = weight
self.__money = money
#通过内部方法,去修改私有属性
#通过自定义的方法实现对私有属性的赋值与取值
#set方法:setxxx
def setMoney(self, money):
#数据的过滤
if money < 0:
money = 0
self.__money = money
#get方法:getXXX
def getMoney(self):
return self.__money
per = Person("hanmeimei", 20, 170, 55, 10000)
#1.属性被私有化之后的访问
#如果要让内部属性不被外部直接访问,在属性前加两个下划线(__),
#在python中如果在属性前面加两个下划线,name这个属性就变成了私有属性[private]
#私有属性的含义:在外界不能像以前那么直接访问
#print(per.__money) #无法在外界直接访问
per.run() #内部可以访问
#2.解决办法: 如何对私有属性取值和赋值
#但是,需要注意的是,在python找那个私有不是绝对的
#属性被私有化之后,可以通过get/set的方法进行访问
per.setMoney(10)
print(per.getMoney())
#4.特殊情况
#在Python中 __xxx__ 属于特殊变量,将不再属于私有变量,可以直接访问
print(per.__age__)
#在python中 _xxx变量,这样的实例变量外部是可以访问的,但是,按照约定的规则
#当我们看到这样的变量时,意思虽然是"虽然我可以被访问,但是请把我视为私有变量,不要直接访问我"
print(per._height)
双下划线开头的变量是不是一定不能够从外部访问呢?其实也不是。
#不能直接访问per.__money是因为python解释器把__money变成了_Person__money
#仍然可以使用_Person__money去访问,但是强烈不建议这么干,不同的解释器可能存在解释的变量名不一致
per = Person("hanmeimei", 20, 170, 55, 10000)
per._Person__money = 1
print(per.getMoney())
总的来说,python本身没有任何机制能够阻止你干坏事,一切靠自觉
注意:这种错误的写法
per = Person("hanmeimei", 20, 170, 55, 10000)
per.__money = 1
print(per.__money)
#从表面上看,外部代码“成功的设置了__money”变量,但是实际上这个__money变量和内部的__money变量不是同一个变量,而是外部的代码给per新增了一个变量
#测试一下
print(per.getMoney())
单下划线,双下划线,头尾下划线的说明
'''
头尾下划线__foo__():定义特殊的方法,一般是系统定义名字,类似于__init__()
单下划线:_foo:以单下划线开头的表示是protected类型的变量,即保护类型的变量只允许本身与子类访问,不能用于from module import *
双下划线:__foo:双下划线的表示的是私有类型(private)的变量,只能允许这个类的本身进行访问。
'''
综合练习
'''
需求:富二代开着骚红色玛莎拉蒂,很自豪的跟朋友炫耀...
分析:
汽车类:
特征:品牌,颜色
行为:在马路上奔驰
富二代类:
特征:姓名
行为:开车,炫耀
'''
form car import Car
from richman import RichMan
#1.创建富二代对象
man = RichMan("王思聪")
#2.创建汽车对象
car = Car("玛莎拉蒂","骚红色")
#富二代的行为
man.dirveCar(car)
man.showCar(car)
class Car(object):
#构造函数
def __init__(self,brand, color):
self.__brand = brand
self.__color = color
#get/set函数
def setBrand(self, brand):
self.__brand = brand
def getBrand(self,brand):
return self.__brand
def setColor(self, color):
self.__color = color
def getColor(self):
return self.__color
#成员函数
def run(self):
print("%s在马路上奔驰"%self.__brand)
class RichMan(object):
#构造函数
def __init__(self, name):
self.__name = name
#get/set函数
def setName(self, name):
self.__name = name
def getName(self):
return self.__name
#成员函数
def driverCar(self,car)
print("福二代%s开着他新车%s"%(self.__name, self.getBrand()))
def showCar(self,car)
print("很自豪的炫耀起来,你看这辆%s,你看这%s...."%(car.getBrand(), car.getColor()))