类和实例
类是对象创建实例的模板,而实例则是对象的实体。类使用class关键字定义:
class MyClass:
? ? pass
python中创建实例直接使用工厂函数(类名加上一对括号),和其他的语言使用new关键字有所不同:
my_obj = MyClass()
一般来说,类名以大写字母开头,而对象名则以小写字母或者下划线开头。
实例化对象时,会执行类定义中的__init__()方法,该方法执行时包含实例的各种初始化操作。
方法和函数的区别:方法由对象调用,在方法定义中,第一个参数必须是显示的self,表示当前对象,而函数则不传入当前对象self,可以说方法是特殊的函数,他指定了调用对象,并且可以获得调用对象的属性。函数和方法都是function类的实例:
class MyClass(object):
? ? def __init__(self):
? ? ? ? pass
? ? def method1(a,b):
? ? ? ? print(a+b)
? ? def method2(self,a,b):
? ? ? ? print(a+b)
obj = MyClass()
MyClass.method1(1,2)
obj.method2(1,2)
print(type(MyClass.method1))
print(type(MyClass.method2))
上述代码中,obj.method2就是方法,而MyClass.method1是一个普通的函数,为什么要使用MyClass.method1是因为method1的命名空间包含在MyClass中,而使用obj.method2的原因是因为method2是obj所在类的一个方法,并obj作为method1调用的对象。
从上述代码还可以延生出以下结论:
1. MyClass.method2(1,2)将会产生错误:TypeError: method2() takes exactly 3 arguments (2 given)。 而MyClass也是一个对象,执行MyClass.method2()时,并不能将MyClass作为self代表的当前对象,这是因为实例的方法必须存在于类中,而实例的属性则存在于实例本身,MyClass调用method2不成功是因为MyClass所在的类:type并没有该方法,所以MyClass.method2()仅仅是执行MyClass命名空间中的一个函数,而该函数要求传入一个对象,所以导致了TypeError错误。
2. obj.method1(1,2)将会产生错误:TypeError: method1() takes exactly 2 positional arguments (3 given)。虽然调用报错,但是报错的原因仅仅是因为参数数量不匹配,由此可见,一个对象调用方法时,该方法必须要显式的第一个对象为self。并且,obj调用时,并没有使用MyClass命名空间,因为obj是MyClass的实例,将会集成MyClass的命名空间。
对象的属性
在python中,实例可以在任何时刻添加对象,即使在类中并没有定义该属性,但是该语句并不会有任何错误,并且在之后一样可以调用该属性,这样的特性可能非常的方便,但是也隐藏了很多弊端,容易对已知的对象传入不必要的属性,从而搞得一团糟:
class MyClass:
? ? def __init__(self,a,b):
? ? ? ? self.a = a
? ? ? ? self.b = b
obj = MyClass(1,2)
print(obj.a)
obj.a = 3
print(obj.a)
def a_method(self,c):
? ? self.c = c
from types import MethodType
obj.a_method = MethodType(a_method,obj)
obj.a_method(4)
print(obj.c)
上述通过类定义之外的途径和对象添加方法和属性,在其他的同类的对象中是不具有的,可以看成是ruby中单件类类似的技巧,通过上述代码给obj添加的方法,实际上存在于obj的单件类中。
通过__slots__可以限制类的属性,以防止对实例添加不必要的变量:
class MyClass:
? ? __slots___ = (‘a’,’b‘)
? ? ...
此时,如果再给obj添加其他的属性将会告知MyClass中没有该属性的错误,从而限制了对象的属性。
对象的属性是不再能被任意添加了,但是仍然可以在外部可以被外部代码轻松访问,通过在变量前添加两个下划线来表示私有变量:
class Student:
? ? __slots__ = (‘score‘,‘grade‘)
? ? def __init__(self,score):
? ? ? ? self.score = score
? ? def getScore(self):
? ? ? ? if self.score >= 90:
? ? ? ? ? ? ?self.grade = ‘A‘
? ? ? ? elif self.score >= 60:
? ? ? ? ? ? self.grade = ‘B‘
? ? ? ? elif self.score < 60:
? ? ? ? ? ? self.grade = ‘C‘
? ? ? ? return self.grade
xiaoming = Student(47)
xiaoming.score = 90
print(xiaoming.getScore())
在上述代码中,虽然在初始化小明的成绩的时候输入了47分,但是仍然可以在外部对该成绩进行修改,由于小明懂了一点python,瞬间就从差等生变成优等生,这样的结果是不希望被看到的。将score改为__score后:
class Student:
? ? __slots__ = (‘__score‘,‘grade‘)
? ? def __init__(self,score):
? ? ? ? self.__score = score
? ? def getGrade(self):
? ? ? ? if self.__score >= 90:
? ? ? ? ? ? self.grade = ‘A‘
? ? ? ? elif self.__score >= 60:
? ? ? ? ? ? self.grade = ‘B‘
? ? ? ? elif self.__score < 60:
? ? ? ? ? ? self.grade = ‘C‘
? ? ? ? return self.grade
此时,小明的投机取巧修改自己分数的手段就不再生效。访问xiaoming.__score时,将出现以下错误:AttributeError: ‘Student‘ object has no attribute ‘__score‘
尽管如此,其实小明还是可以这样做:
xiaoming._Student__score = 100
因为在使用双下划线时,python只不过是讲变量的名字之前加入了_Classname前缀以避免被直接访问到(不同版本的解释器会有所不同),而不是真正的不可访问,python中并没有真正不可操作的机制,而类似于_Student这些单下划线开头的变量名,虽然可以被外部访问到,但是最好不要这样做,单下划线的意思就是,虽然我不是私有变量,但是请你把我当成私有变量,这或许是对python中没有私有变量的一种妥协吧。
此时,我也没有办法来对付小明了。这是很有意思的故事,在Ruby中,任何实例变量在设置set方法和get方法之前,都是不能被外部访问到的,只有通过给私有变量设置setter方法或者getter方法,该变量才真正的成为属性,总之为了实例变量成为属性要做一些必要工作。而python中,几乎不能阻止一个对象访问在类中定义的变量,甚至如果你不用__slots__,都无法阻止添加各种属性到对象中,我们要做的工作反而变成想方设法让属性不能被访问到。
使用真正的属性
python也可以和其他语言一样,通过@property修饰器,实现getter、setter方法从而访问和设置的属性,从而可以“掩耳盗铃”地说,你不能设置该属性:
class Student:
? ? __slots__ = (‘_score‘,‘_grade‘)
? ? def __init__(self,score):
? ? ? ? self._score = score
? ? @property
? ? def score(self):
? ? ? ? return _score
? ? @property
? ? def grade(self):
? ? ? ? if self._score >= 90:
? ? ? ? ? ? self._grade = ‘A‘
? ? ? ? elif self._score >= 60:
? ? ? ? ? ? self._grade = ‘B‘
? ? ? ? elif self._score < 60:
? ? ? ? ? ? self._grade = ‘C‘
? ? ? ? return self._grade
当使用了@property修饰器之后,其实就是实现了getter方法,使外部可以被看到,如果想要设置getter方法,则再添加:
? ? @score.setter
? ? def score(self,value):
? ? ? ? score._score = value
和其他语言一样,方法名字就是属性名字,小明可以通过xiaoming.score来看到自己的成绩,当没有定义setter方法时,小明是不能通过xiaoming.score来修改自己的成绩的,但是这是不是就意味着小明真的不能改了呢?其实通过上面的代码已经可以看到,python内部的变量名字并不是score,而是_score,这就是为什么我说“掩耳盗铃”的原因,小明依然可以通过xiaoming._score来修改自己的成绩。总之,python中是不会真正阻止一个对象访问类定义中的变量的,这实在有点令人苦恼。?