在面向对象编程中,构造函数和访问控制是我们经常用到的,并且是面向对象很重要的特性。
虽然我们需要类的所有实例拥有共同的属性, 但是我们不想让他们拥有共同的属性值,在对象创建后,我们可以调用一个函数来修改这些属性值。
class Human(): def initdata(self, myname, myage): self.name = myname self.age = myage def speak(self): print("大家好,我是%s;我的年龄是%s" % (self.name, self.age)) luren_a = Human() luren_a.initdata("ruhua", 18) luren_a.speak() luren_b = Human() luren_b.initdata("zhaoritian", 17) luren_b.speak()
我们发现上面那种方法重复繁琐,Python 语言可以通过定义一个名称为 __init__ 的函数,该函数叫做构造函数,在我们创建对象的时候,Python 解释器会自动调用构造函数。构造函数也是类的成员函数,至少也要拥有一个参数,我们知道第一个参数是用来接收调用对象本身的,在构造函数内部,我们可以把各种属性绑定到该参数上。
class Human(): def __init__(self, myname, myage): self.name = myname self.age = myage def speak(self): print("大家好,我是%s;我的年龄是%s" % (self.name, self.age)) luren_a = Human("ruhua", 18) # Python 解释器会自动调用构造函数,并把 luren_a 作为第一个实参传给构造函数 luren_a.speak() luren_b = Human("xingxing", 17) # Python 解释器会自动调用构造函数,并把 luren_b 作为第一个实参传给构造函数 luren_b.speak()
由于在构造函数中我们把调用构造函数的对象本身传过去,然后通过这个对象初始化属性,这样一来,可以实现每个对象拥有不同的属性值。
class Human(): def __init__(self, myname): self.name = myname luren_a = Human("ruhua") luren_b = Human("xingxing") print(luren_a.name) # luren_a 对象的 name print(luren_b.name) # luren_b 对象的 name
假如我们定义一个圆类,对于所有的圆类产生的对象都有 π 这个属性,并且该属性对所有圆对象的值都是一样的, 我们没必要把 π 这个属性在每个对象创建的时候在构造函数内重新赋值,因为他们是一样的,我们可以把该属性定义在构造函数外面,我们称为该属性是属于类的(也就是所有对象共有一个)。
class Circle(): PI = 3.1415926 def __init__(self, radius): self.radius = radius def showarea(self): print(self.radius * self.PI) redcircle = Circle(3) redcircle.showarea() # 用的是类的属性 PI bluecircle = Circle(5) # 用的是类的属性 PI bluecircle.showarea() Circle.PI = 1 # 可以通过类改变属性 PI print(redcircle.PI) # 用的是类的属性 PI print(bluecircle.PI) # 用的是类的属性 PI
Python 如何实现让所有对象共享一个属性的呢?很简单,当 Python 发现类内存在属于类的属性时,只需要在每个对象初始化时,让对象中同名属性指向类属性即可。 因此,我们试图通过某个对象修改属于类的属性,则对其它对象不起作用,因为修改的只是这个对象属性本身的指向,所有的其它对象属性还在指向这个属于类的属性。
class Circle(): PI = 3.1415926 def __init__(self, radius): self.radius = radius redcircle = Circle(3) bluecircle = Circle(5) redcircle.PI = 1 # 只是改变了 redcircle 对象的属性 PI 指向 print(redcircle.PI) # 1 print(bluecircle.PI) # bluecircle 对象的属性 PI 还在指向 Cirle 类的属性 PI print(Circle.PI) # Circle 类的属性 PI 只能通过 Cirle 类来改变
我们在进行面向对象编程时,最好不要直接通过对象来访问(读或写)属性,而是通过类提供的函数进行读写属性操作,要养成良好的编程习惯。
class Human(): def __init__(self, myname): self.name = myname def getname(self): return self.name def setname(self, newname): self.name = newname luren_a = Human("ruhua") print(luren_a.getname()) # 访问属性 luren_a.setname("wanghuahua") # 修改属性 print(luren_a.getname())
人的天性都是非自觉的,程序员也是人,所以需要约束,我们刚刚讲过最好不要通过实例直接访问类的属性, 那只是要求程序员自觉遵守,所以我们可以强制性约束,为了让内部属性不被外部访问, 我们可以在属性的名称前加上两个下划线 __,在 Python 中,实例的变量名如果以 __ 开头,就变成了一个私有变量(private),私有变量只有在类的内部可以访问(读写),在类的外部不能访问(读写)。
class Human(): def __init__(self, myname): self.__name = myname def getname(self): return self.__name # 类的内部可以访问 def setname(self, newname): self.__name = newname # 类的内部可以访问 luren_a = Human("ruhua") print(luren_a.getname()) luren_a.setname("wanghuahua") print(luren_a.getname()) print(luren_a.__name) # 报异常,因为类的外部无法访问 luren_a.__name = "wanghuahua" # 报异常,因为类的外部无法访问
有时候,我们会遇到以单下划线开头的变量,这样的实例变量在外部是可以访问的,我们最好不要去访问,这是一个不成文的约定。
class Human(): def __init__(self, myname): self._name = myname luren_a = Human("ruhua") print(luren_a._name) # 类的外部可以访问 _name,但最好不要访问,要遵守约定
Python 本身不进行访问控制的语法检查,以双下划线开头的变量不能访问的原因,
仅仅是因为 Python 在类内部把所有以 __
开头的变量进行了更名,我们如果知道更改后的属性名,就可以在外部进行访问该属性,注意不同版本的
Python 解释器可能会把该变量改成不同的变量名。
class Human(): def __init__(self, myname): self.__name = myname luren_a = Human("ruhua") print(luren_a._Human__name) # Python 3.7 版本把名字改成了 _Human__name Human.__name = "wanghuahua" print(luren_a.__name) # Python 只对类内部 __开头的变量做改名,所以可以访问 # 我们可以通过 id 函数知道它们并不是一个变量 print(id(luren_a.__name)) print(id(luren_a._Human__name))
构造函数如何定义
Python 实现访问控制的方法
著名公司(某软)笔试题:给某个公司设计一个职工类,所有职工都是中国人,大部分是男性,类里面有属性(姓名,性别,国籍,月薪,婚否), 类里面有方法(根据姓名(可以重名)获得职工的年薪,对所有职工按年薪做排序,获取男性员工和女性员工所占人数的比例,获取已结婚的中国籍员工人数)。
在C++中的同类名的构造函数,其功能是相当于_init还是相当于__new__?
难道说__new_和_init__的区别在于前者可以访问静态变量而后者不可以??
为什么很多人认为Python容易,甚至高中生都要学,然后参加考试。而熟练使用C/C++,Java,Go的我,认为Python的难度远大于前三者。各种各样的语法糖充斥这Python,庞大的约定机制,作者似乎脑子想到一个什么约定就去毫无道理地实现它,完全没有前面三者的简洁。再加上解释型语言的难调试性,IDE的不友好性,让我对Python简直深恶痛绝。
昨天看python面向对象编程中有提过这个,不过很少用,new方法一般是用在元编程
明白了,不过一般用__ini__就可以了吧,不需要在实现__new__方法