[Python] 08 - Classes --> Objects
故事背景
一、阶级关系
1. Programs are composed of modules.
2. Modules contain statements.
3. Statements contain expressions.
4. Expressions create and process objects.
二、教学大纲
-
<Think Python>
-
菜鸟教程
Goto: http://www.runoob.com/python3/python3-class.html
参考资源:廖雪峰,Python面向对象编程
参考资源:廖雪峰,Python面向对象高级编程
参考资源:错误、调试和测试
-
面向对象的三大特点
数据封装(Encapsulation )、继承(inheritance )和多态(polymorphism)。
Encapsulation
类的定义
-
构造方法
赋值的的过程,就自动完成了类内的变量定义。
#!/usr/bin/python3 class Complex: def __init__(self, realpart, imagpart): self.r = realpart self.i = imagpart
x = Complex(3.0, -4.5) print(x.r, x.i) # 输出结果:3.0 -4.5
self 参数
类的方法与普通的函数只有一个特别的区别——它们必须有一个额外的 第一个参数名称:self
注意:self 的名字并不是规定死的,也可以使用 this,但是最好还是按照约定使用 self。
self 记录了 “类的地址”,“类的属性” 等各种类的属性。
class Test: def prt(self): print(self) # address print(self.__class__) # name t = Test() t.prt()
执行结果:
<__main__.Test instance at 0x100771878> __main__.Test
权限控制
-
“私有” 属性 & 方法
加上 “双下划线” 定义属性为私有属性。
#!/usr/bin/python3 class people: name = '' age = 0 __weight = 0 def __init__(self, n, a, w): self.name = n self.age = a self.__weight= w def speak(self): print("weight is {}".format(self.__weight)) def __talk(self): print("age is {}".format(self.age)) p = people('runoob', 10, 30) ##################### # private attribute ##################### print(p.name) # print(p.__weight) # <-- this is err case. print(p._people__weight) ##################### # private function ##################### p.speak() # p.__talk() # <-- this is err case. p._people__talk()
“私有化” 原理
双下划线开头的实例变量是不是一定不能从外部访问呢?其实也不是。不能直接访问__name
是因为Python解释器对外把__name
变量改成了_Student__name
,所以,仍然可以通过_Student__name
来访问__name
变量:
>>> bart._Student__name 'Bart Simpson'
但是强烈建议你不要这么干,因为 “不同版本的Python解释器可能会把__name
改成不同的变量名”。
总的来说就是,Python本身没有任何机制阻止你干坏事,一切全靠自觉。
-
限制滥用 “对象成员”
__slot__关键字
由于'score'
没有被放到__slots__
中,所以不能绑定score
属性,试图绑定score
将得到AttributeError
的错误。
注意:使用__slots__
要注意,__slots__
定义的属性仅对当前类实例起作用,对继承的子类是不起作用。
class Student(object): __slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称 >>> s = Student() # 创建新的实例 >>> s.name = 'Michael' # 绑定属性'name' >>> s.age = 25 # 绑定属性'age' >>> s.score = 99 # <---- 绑定属性'score' Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: 'Student' object has no attribute 'score'
为何用?内存环保
Ref: 10. __slots__ Magic
动态语言意味着“浪费空间”换取“灵活性”。显然,减少不必要的灵活性可以 reduce memory of RAM。添加__slots__后带来的内存环保效果如下:
The code will reduce the burden on your RAM. Some people have seen almost 40% to 50% reduction in RAM usage by using this technique.
class MyClass(object): __slots__ = ['name', 'identifier'] def __init__(self, name, identifier): self.name = name self.identifier = identifier self.set_up() # ...
-
成员变量 Set & Get
过去的策略
分别定义set, get函数操作。
装饰器策略
将函数当做属性来使用:
(a) 只有@property表示只读。
(b) 同时有@property和@x.setter表示可读可写。
(c) 同时有@property和@x.setter和@x.deleter表示可读可写可删除。
class student(object): def __init__(self,id): self.__id=id @property # 只读 def score(self): return self._score @score.setter # 只写 def score(self, value): # 设置前的checker if not isinstance(value,int): raise ValueError('score must be an integer!') if value<0 or value>100: raise ValueError('score must between 0 and 100') # 开始设置 self._score=value @property # 只读 def get_id(self): return self.__id s=student('123456') s.score=100 # 写 print(s.score) # 读 print(s.__dict__) print (s.get_id) # 只读 #s.get_id=456 #只能读,不可写: AttributeError: can't set attribute
# 相较于”经典方式“,以下是新式简便方式,新式类(继承自object类)
class A(object): def __init__(self): self.__name=None def getName(self): return self.__name def setName(self,value): self.__name=value def delName(self): del self.__name name=property(getName, setName, delName) a=A() print(a.name) # 读 a.name='python' # 写 print(a.name) # 读 del a.name # 删除
类方法
-
@staticmethod 与 @classmethod
类方法的 3种定义方式
尤其是第一种,比较tricky。
class Person(object): # [第壹种方法] 不加任何参数直接定义,也是类方法 def Work(): print(" I am working!")
# [第贰种方法] 加装饰器方法 @classmethod def Think(cls, b): # 类方法Think必须要带至少1个参数,第一个参数默认为类名,后面可以引用。 cls.Eat(b) # 在类方法Think中,调用类方法Eat类方法。 cls.Work() # 在类方法Think中,调用Work类方法。 print(b,",I am Thinking!")
# [第叁种方法] 先定义类方法,至少1个参数,第一个默认为类名 def Eat(cls, b): print(b+",I am eating") Eat=classmethod(Eat) # 通过内建函数classmethod()来创建类方法。
类的三种方法
如下展示了三种方法:普通类成员函数、静态方法、类方法。
# # class one. # class Person(object): grade=6 def __init__(self): self.name = "king" self.age=20 # ----------------------------------------------------------------------- def sayHi(self): print ('Hello, your name is?',self.name) def sayAge(self): print( 'My age is %d years old.'%self.age) # ----------------------------------------------------------------------- @staticmethod # 静态方法 def sayName(): print ("<staticmethod> my name is king") # ----------------------------------------------------------------------- @classmethod # 类方法 def classMethod(cls): print('<classMethod> class grade:',cls.grade)
该类的用法如下:
p = Person() # 实例化对象 print('p.grade:',p.grade) # 实例对象调用类变量 p.grade=9
# 类和对象都可以调用 @classmethod,但这里类的grade不会改变,体现了独立性 p.classMethod() Person().classMethod()
# __init__的变量不属于类
# 但对象找不到变量,就会去找类里的
Person.age Person().age Person.__dict__ Person().__dict__
# 概念辨析:类对象、实例对象 Person().grade # 类对象调用类变量 p.sayHi() # 实例对象调用类成员函数 Person().sayAge() # 类对象调用类成员函数 # 静态变量任意调用 m=Person() m.sayName() # 多个实例皆可共享此静态方法 Person().sayName() # 类对象调用静态方法
继承区别
子类的实例继承了父类的static_method静态方法,调用该方法,还是调用的父类的方法和类属性。
子类的实例继承了父类的class_method类方法,调用该方法,调用的是子类的方法和子类的类属性。
Ref: python类的实例方法、静态方法和类方法区别及其应用场景
class Foo(object): X = 1 Y = 14 @staticmethod def averag(*mixes): # "父类中的静态方法" return sum(mixes) / len(mixes) @staticmethod def static_method(): # "父类中的静态方法" print "父类中的静态方法" return Foo.averag(Foo.X, Foo.Y) @classmethod def class_method(cls): # 父类中的类方法 print "父类中的类方法" return cls.averag(cls.X, cls.Y) class Son(Foo): X = 3 Y = 5 @staticmethod def averag(*mixes): # "子类中重载了父类的静态方法" print "子类中重载了父类的静态方法" print "666 ",mixes return sum(mixes) / 3 p = Son() print "result of p.averag(1,5)" print (p.averag(1,5)) print "result of p.static_method()" print(p.static_method()) print "result of p.class_method()" print(p.class_method())
-
cls参数 - Factory method
构造函数的重载
Ref: class method vs static method in Python
We generally use class method to create factory methods. Factory methods return class object ( similar to a constructor ) for different use cases.
We generally use static methods to create utility functions.
为何这里提到了”工厂方法“?因为工厂方法需要返回类,而类方法是'天生的”具备cls参数。
实例中通过两种策略返回类,等价于:不用“函数重载”,实现多个版本的”构建函数“。
Goto: 为什么 Python 不支持函数重载?而其他语言大都支持?
############################# # cls: factory method ############################# from datetime import date class Person: def __init__(self, name, age): self.name = name self.age = age @classmethod def fromBirthYear(cls, name, birthYear): return cls(name, date.today().year - birthYear)
# 模拟上面的类方法,但第一个参数在使用时,省不掉
def fromBirthYear2(cls, name, birthYear):
return cls(name, date.today().year - birthYear)
#---------------------------------------------------
def display(self): print(self.name + "'s age is: " + str(self.age)) # (1) 常规方式返回一个实例对象 person = Person('Adam', 19) person.display() # (2) 类方法返回一个实例对象 person1 = Person.fromBirthYear('John', 1985) person1.display()
# (3) 模拟类方法的使用,只是不美观而已
person2 = Person.fromBirthYear2(Person, 'Jeff', 1987)
person2.display()
Python实现工厂方法
Goto: [Design Patterns] 01. Creational Patterns - Abstract Factory
重要区别:"抽象工厂"的工厂是类;"工厂方法"的工厂是方法。
类的字典属性
-
查询 “对象” de 属性&方法
>>> # var public >>> hasattr(p, 'name') True
>>> # var private>>> hasattr(p, '__weight') False >>> hasattr(p, '_people__weight') True
>>> # func public>>> hasattr(p, 'speak') True >>> fnSpeak = getattr(p, 'speak') # <-- func pointer. >>> fnSpeak() weight is 30
>>> # func private>>> hasattr(p, '__talk') False
>>> getattr(p, '__talk', 404) # 如果不存在,返回默认值404
404 >>> hasattr(p, '_people__talk') True >>> fnTalk = getattr(p, '_people__talk') # <-- func pointer >>> fnTalk() age is 10
查询 “类” de 属性&方法
类貌似不能直接调用方法。因为方法需要self,而 self是实例化后成为对象,才会有的东西。
对象属性和类属性虽然不是一个东西,但类的属性会成为对象的 ”备份“,如下所示。
>>> class Student(object): ... name = 'Student' >>> s = Student() # 创建实例s >>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性 Student >>> print(Student.name) # 打印类的name属性 Student >>> s.name = 'Michael' # 给实例绑定name属性 >>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性 Michael >>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问 Student >>> del s.name # 如果删除实例的name属性 >>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了 Student
-
设置 “对象” de 属性&方法
这里,主要关注 class 与 object 之间的趣味关系。
实例化后,两者就操作独立了。
>>> getattr(people, 'age') 0
>>> p = people('p hao', 20, 100) >>> p2 = people('p2 hao', 30, 200) >>> p.age 20 >>> p2.age 30
>>> setattr(people, 'age', 88)
>>> getattr(people, 'age') 88 >>> p.age 20 >>> p2.age 30
>>> delattr(people, 'age') >>> hasattr(people, 'age') False >>> getattr(people, 'age') Traceback (most recent call last): File "<stdin>", line 1, in <module> AttributeError: type object 'people' has no attribute 'age'
>>> p.age 20 >>> p2.age 30
-
“字典属性” 原理
如果我们在Class没有定义这3个方法,那么系统会用Python自带的内部函数;
如果我们在类里面自定义了这3个方法,那么python会先调用我们自己定义的这3个函数。
self.__dict__
Ref: 到底干了什么?
这里要小心“无限循环”!需要通过最为本质的操作方法:__dict__操作。
class Cat: class_level = '贵族' def __init__(self,name,type,speed,age): self.name = name self.type = type self.speed = speed self.age = age def run(self): print('%s岁的%s%s正在以%s的速度奔跑' % (self.age, self.type, self.name, self.speed))
def __getattr__(self, item): print('你找的属性不存在') def __setattr__(self, key, value): print('你在设置属性') # self.key=value # !!! 这种方法不行,会产生无限递归了,因为他本身self.key=value也会触发__setattr__ self.__dict__[key] = value def __delattr__(self, item): print('你在删除属性') # del self.item # !!! 无限递归了,和上面的__setattr__原理一样 self.__dict__.pop(item) xiaohua = Cat('小花','波斯猫','10m/s',10)
可见,obj 中没有给出函数相关信息。
>>> people.__dict__ mappingproxy({'__module__': '__main__', 'name': '', 'age': 0, '_people__weight': 0, '__init__': <function people.__init__ at 0x7f1659e8b488>, 'speak': <function people.speak at 0x7f1659e8b510>, '_people__talk': <function people.__talk at 0x7f1659e8b598>, '__dict__': <attribute '__dict__' of 'people' objects>, '__weakref__': <attribute '__weakref__' of 'people' objects>, '__doc__': None}) >>> p3 = people('p hao', 20, 100) >>> p3.__dict__ {'name': 'p hao', 'age': 20, '_people__weight': 100}
常见字典属性
(1) 简单的若干属性
类的专有方法:
__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__ : 按照索引获取值
__len__ : 获得长度
__cmp__ : 比较运算
__call__: 函数调用
__add__ : 加运算
__sub__ : 减运算
__mul__ : 乘运算
__div__ : 除运算
__mod__ : 求余运算
__pow__ : 乘方
其他generic的方法:
'__module__': '__main__', # 当导入到其他的脚本文件的时候,此时__name__的名字其实是导入模块的名字,不是’__main__’, main代码里面的就不执行了。
'__dict__': <attribute '__dict__' of 'people' objects>, '__weakref__': <attribute '__weakref__' of 'people' objects>, '__doc__': None})
(2) __str__, __repr__ 的区别
Ref: 廖雪峰 - 定制类
__repr__() 方法是类的实例化对象用来做“自我介绍”的方法,默认情况下,它会返回当前对象的“类名+object at+内存地址”,而如果对该方法进行重写,可以为其制作自定义的自我描述信息。
(a) 两者的笼统区别在于:__str__,用户友好;__repr__,程序员友好。
print('hello'.__str__()) hello print('hello'.__repr__()) 'hello' from datetime import datetime as dt print(dt.today().__str__()) 2018-11-30 14:26:38.881492 # 返回的是处理后的str数据 print(dt.today().__repr__()) datetime.datetime(2018, 11, 30, 14, 26, 48, 580276) # 返回的是原始函数调用的数据方法
(b) 为什么要用?打印类的效果更为美观。
class Chain(object): def __init__(self, path=''): self._path = path def __getattr__(self, path): return Chain('%s/%s' % (self._path, path)) def __str__(self): return self._path __repr__ = __str__ c = Chain('hello world')
print(c) # call __str__
hello world
c # call __repr__
hello world
(c) 进一步配合使用实现:链式调用。
# (1) try to find this attribute --> __getattr__ # (2) go to the next Chain by 'return', and print attribute name --> __repr__ Chain().status.user.timeline.list
(3) __call__ 将类的用法 "函数化"
函数后的 ”小括号“ 触发了__call__。
class Student(object): def __init__(self, name): self.name = name def __call__(self): print('My name is %s.' % self.name)
>>> s = Student('Michael') >>> s() My name is Michael.
那么,怎么判断一个变量是对象还是函数呢?通过callable()
函数,我们就可以判断一个对象是否是“可调用”对象。
>>> callable(Student()) True >>> callable(max) True >>> callable([1, 2, 3]) False >>> callable(None) False >>> callable('str') False
-
运算符重载
(a) 类的加法
关键字:__add__
#!/usr/bin/python3 class Vector:
def __init__(self, a, b): self.a = a self.b = b def __str__(self): return 'Vector (%d, %d)' % (self.a, self.b) def __add__(self, other): return Vector(self.a + other.a, self.b + other.b) v1 = Vector(2,10) v2 = Vector(5,-2) print (v1 + v2)
执行结果:
Vector(7,8)
(b) Iterable类
定义一个可遍历的类。或者具备generator特性的类,如下。
class Fib(object):
def __init__(self): self.a, self.b = 0, 1 # 初始化两个计数器a,b def __iter__(self): return self # 实例本身就是迭代对象,故返回自己 def __next__(self):
# (1) 计算下一个值 self.a, self.b = self.b, self.a + self.b
# (2) 注意 "循环退出条件" if self.a > 100000: raise StopIteration()
return self.a # 返回下一个值 # Fib()返回的是自己本身;迭代的则是__next__提供的结果。 >>> for n in Fib(): ... print(n) ... 1 1 2 3 5 ... 46368 75025
(c) 数组模式
本质上是通过重新计算得出结果。
class Fib(object):
def __getitem__(self, idx): a, b = 1, 1 for x in range(idx): a, b = b, a + b return a >>> f = Fib() >>> f[0] 1 >>> f[1] 1 >>> f[2] 2 >>> f[3] 3 >>> f[10] 89 >>> f[100] 573147844013817084101
(d) 切片模式
class Fib(object):
def __getitem__(self, n): if isinstance(n, int): # n是索引 a, b = 1, 1 for x in range(n): a, b = b, a + b return a if isinstance(n, slice): # n是切片 start = n.start stop = n.stop if start is None: start = 0 a, b = 1, 1 L = [] for x in range(stop): if x >= start: L.append(a) a, b = b, a + b return L >>> f = Fib() >>> f[0:5] [1, 1, 2, 3, 5] >>> f[:10] [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
与之对应的是__setitem__()
方法,把对象视作list或dict来对集合赋值。
最后,还有一个__delitem__()
方法,用于删除某个元素。
Inheritance
多继承
-
构造函数
方法一、可以通过直接 ”调用父类构造函数" 来实现。
parent.__init__(self, ...)
方法二、还有下面这个方式,至少不用再涉及到"父类”的名字,貌似好一些。
super(子类,self).__init__(参数1,参数2,....)
-
多继承歧义
Ref: [c++] Class [C++参考]
(1) 菱形:近三层的路径二义性
钻石继承时,c++中采用虚继承解决;python 怎么办?使用 super(子类, self) 方式。
[C++ 策略]
采用虚继承,自然就没了路径二义性,因为不care这个 “虚路径” 也就没事了。
[Python 策略]
不直接调用parent相关的东西,而是采用 super,只提及自己的类名,这里避开了选择哪个 of “两个父类名字” 的问题
class Father(object): def __init__(self, name, *args, **kwargs): self.name = name print("我是父类__init__")
class Son_1(Father): def __init__(self, name, age, *args, **kwargs): print("我是Son_1的__init__") super(Son_1, self).__init__(name, *args, **kwargs) self.age = age class Son_2(Father): def __init__(self, name, gender, *args, **kwargs): print("我是Son_2的__init__") super(Son_2, self).__init__(name, *args, **kwargs)
self.gender = gender
class GrandSon(Son_1, Son_2): def __init__(self, name, age, gender):
print("我是GrandSon的__init__") super(GrandSon, self).__init__(name, age, gender) def say_hello(self): print(self.name, self.age, self.gender)
grand_son = GrandSon("老王", 24, "男")
(2) 倒三角:近两层的调用歧义
若是两个父类中有相同的方法名,而在子类使用时未指定,python 从左至右 搜索 即方法在子类中未找到时,从左到右查找父类中是否包含方法。
也就是说:优先选择位置“靠左”所继承的父类的方案。
class Father(object): def __init__(self, name, *args, **kwargs): self.name = name print("我是父类__init__") def say_hello(self): print("i'm father.") class Son_1(Father): def __init__(self, name, age, *args, **kwargs): print("我是Son_1的__init__") super(Son_1, self).__init__(name, *args, **kwargs) self.age = 100 def say_hello(self): print("i'm son_1.") def fn_son_1(self): print("only son 1 can do it, age = {}".format(self.age) ) class Son_2(): def __init__(self, name, gender, *args, **kwargs): print("我是Son_2的__init__") self.gender = gender # super(Son_2, self).__init__(name, *args, **kwargs) def say_hello(self): print("i'm son_2.") class GrandSon(Son_2, Son_1): def __init__(self, name, age, gender): print("我是GrandSon的__init__")
# super(GrandSon, self).__init__(name, age, gender) Son_2.__init__(self, name, age, gender) Son_1.__init__(self, name, age, gender) grand_son = GrandSon("老王", 24, "男") grand_son.fn_son_1()
Notice: GrandSon在这里是不宜使用super的,这种“自动化模式”会导致“只调用Son_2的__init__。
所以,这里还是使用了传统的 父类.__init__,这样,grand_son.fn_son_1()就可以正常执行了。
Polymorphism
家长代表权
-
"父类" 作为参数
[C++ 策略]
采用虚函数,纯虚函数(实现接口)。
[Python 策略]
该参数也可以兼容处理其子类。以下,对应了C++的虚函数。
class Father(object): def __init__(self, name, *args, **kwargs): self.name = name print("我是父类__init__") def say_hello(self): print("i'm father.") class Son_1(Father): def __init__(self, name, age, *args, **kwargs): print("我是Son_1的__init__") super(Son_1, self).__init__(name, *args, **kwargs) self.age = 100 def say_hello(self): print("i'm son_1.") def fn_son_1(self): print("only son 1 can do it, age = {}".format(self.age) ) class Son_2(Father): def __init__(self, name, gender, *args, **kwargs): print("我是Son_2的__init__") self.gender = gender super(Son_2, self).__init__(name, *args, **kwargs) def say_hello(self): print("i'm son_2.") son_1 = Son_1("son 1", 24) son_2 = Son_2("son 2", 'male')
设计一个函数,一并兼容处理son_1, son_2,怎么办?
>>> def say_something(father): ... father.say_hello()
>>> say_something(son_1) i'm son_1. >>> say_something(son_2) i'm son_2.
-
"类父类" 作为参数
动态语言支持“鸭子类型”,具备“必要的”方法就能凑活的用了。
对于Python这样的动态语言来说,则不一定需要传入Animal
类型。我们只需要保证传入的对象有一个run()
方法就可以了:
class Timer(object): def run(self): print('Start...')
End.