【Python编程与数据分析】面向对象
面向对象
对象
- Python 支持多种不同数据类型
- 以上所有数据都是一个对象, 每个对象都有:
• 身份(identity),即是存储地址,可以通过id()这个方法来查询
• 类型(type),即对象所属的类型,可以用type()方法来查询
• 值,都会有各自的属性、方法 - 一个对象是一个类型的实例(instance)
面向对象编程(OOP)
- Python中,一切皆对象(并有一个类型)
- 可以创建某一类型的对象
- 可以对 对象进行操作
- 可以销毁对象
- 显式的使用del 或者 不去管它
- python 系统会 回收被销毁或者不可读取的对象-被称为“垃圾回收机制”
如何理解一切皆对象
- 在面向对象体系里面,存在两种关系:
- 父子关系,即继承关系: python里要查看一个类型的父类,使用它的__bases__属性可以查看
- 类型实例关系: 使用它的__class__属性可以查看,或者使用type()函数查看
- object是继承关系的顶端,object是所有类型的父类(直接或间接)
- type是类型实例关系的顶端,所有对象都是它的实例的
- 二者关系:
- object是type的一个实例
- Type是object的子类
>>>object.__class__
>>>type(object)
#type
>>>object.__bases__
#()
#object是继承关系顶端,没有父类
>>> type.__bases__
#(object,)
#type是object的子类
>>> type.__class__
#type
#type的类型是type
5.
面向对象的好处
- 将数据和方法进行打包 通过接口提供给使用者
- 分而治之(divide-and-conquer) 的开发
- 可以独立的开发和测试每个类
- 使程序更加模块化,降低复杂度
- 类(classes) 让代码复用更容易
- 很多Python的模块都有自定义的新类
- 每个类都有自己独立的环境和命名(不与其他函数名冲突)
- 使用继承使得子类重定义或者扩展父类的一些行为
创建和使用类的区别
- 创建一个类有别于使用类来创建一个实例(instance)
- 创建 类包括: 定义类名;定义类的属性、方法;例:写一段实现 list的代码
- 使用 类包括:创建类的实例;对实例进行操作;例,
L=[1,2]
和len(L)
自定义类(类型)
class
关键字 :定义一个新的类型
什么是属性(attributes)
- 类中的数据和程序
- 数据属性
- 构成类的数据对象
- 例: Coordinate 类 由 2个数(坐标)构成
- 方法 (程序属性)
- 方法就是类中的函数,只作用于这个类或类的实例
- 与对象进行交互
- 例:在coordinate类中定义一个 distance的方法,求两个坐标对象的距离,但distance无法作用在其他类型的对象上,比如list
创建类的实例
- 构造方法(constructor):定义如何创建一个类的实例
- 使用特殊方法:
__init__
初始化一些数据属性
- 定义好了类之后,创建一个实例吧
- 实例中的数据属性,比如c.x中的x,叫做实例变量
- 不用传递任何值给self,python会自动处理
什么是方法(method)
- 方法是类中的程序属性,一个只作用于这个类的function(函数)
- Python总是会把对象当作第一个参数: 惯例是把self当作所有方法的第一个参数
- “.” 操作符 可以获取到任意属性: • 对象的实例属性 • 对象的方法(method)
写一个Coordinate类的方法
如何使用方法
2.
打印一个对象
>>> c = Coordinate(3,4)
>>> print(c)
#<__main__.Coordinate object at 0x7fa918510488>
- 直接打印对象时,无法打印出有效信息
- 给类定义一个__str__方法
- 使用print()打印对象时,Python 会调用__str__方法
- 可以自定义打印的内容和格式! 比如打印Coordinate对象时, 我们想要
>>> print(c)
<3,4>
自定义打印方法
类和类型(Types)
2. 实现类 VS 使用类 | 类定义 VS 类的实例
GETTER AND SETTER 方法
不能从外部访问的属性、方法
一个实例 和 .(dot)符号
- 一个对象的实例的创建叫做实例化:
a = Animal(3)
- .符号用来获取属性(数据和方法) ,但是最好使用getters和setters来获取数据属性
为何不用.符号:信息隐藏
Python的信息隐藏并不到位
- 以下的行为均不提倡!
- 允许从类定义的外部获取数据:
print(a.age)
- 允许从类定义的外部写入数据:
a.age = 'infinite’ #age本应是一个整数
- 允许从类定义的外部创建新的数据属性:
a.size = "tiny" #(size并没有在类中定义)
方法的默认参数
类的层级:继承
父类(Parent class)
子类
class Cat(Animal): # 继承所有Animal类的属性
def speak(self): # 添加新的方法speak()
print("meow")
def __str__(self): # 重写/复写(overrides)__str__
return "cat:"+str(self.name)+":"+str(self.age)
- 添加一个新的功能/行为 speak()
- Cat 类型的实例可以调用新的方法
- Animal类型的实例如果调用新方法speak() 会抛出异常(throws error)
- init 构造方法并没有消失,从Animal父类中继承
实例使用的是哪个方法?
• 子类中的方法可以与父类中的方法同名
• 对于一个类的实例,会在自己的类定义中寻找方法名
• 如果没找到,去上级父类中寻找方法名(先在父类中找,然后去“爷爷”类中找,以此类推)
• 调用最先找到的那个方法
类变量(class variable)
- 所有的实例共享类变量(class variables)
- 类变量 vs 成员变量
Rabbit GETTER 方法(注意.zfill()方法)
魔法方法重载
- 比较两个Rabbits的特殊方法
运算符重载的魔法方法
super()
- super()是python内置函数
- 单继承中,super()主要是用来调用父类的方法
class A:
def method(self):
print("I am in parent")
class B(A):
def method(self):
print("I am in child.")
super().method() # 通过super()调用父类A中的method方法
b = B()
b.method()
#I am in child.
#I am in parent
魔法方法重载
2.
class Mycls:
def __new__(cls):
print('new')
return super().__new__(cls)
def __init__(self):
print('init’)
#用Mycls类创建一个实例化对象my
my=Mycls()
#new
#init
- __new__方法的应用场景(eg. 单例模式)
- __call__方法
- 该方法的功能类似于在类中重载 () 运算符,使得类实例对象可以像调用普通函数那样,以“对象名()”的形式使用
class Mycls:
def __call__(self,name,age): # 定义__call__方法
print("调用__call__()方法",name,age)
person = Mycls()
person("你儿子","8岁")
#调用__call__()方法 你儿子 8岁
• 可以看到,通过在 Mycls 类中实现 __call__() 方法,使的 person 实例对象变为了可调用对象。
• person(“你儿子”,“8岁”) 等同于 person.__call__ ("你儿子","8岁")
抽象类
- 抽象类:
- 职责:包含子类应该实现的一组抽象方法
- 特征:不能实例化
- 抽象类 : register的用法(左) 和 注意事项(右)
- 静态方法
- 类方法
- str()方法和__repr__()方法
- str()和__repr__()都是用来显示/打印的
- print()方法打印一个实例对象时,首先会尝试__str__(),
- Python 定义了__str__()和__repr__()两种方法,str()用于显示给用户,而__repr__()用于显示给开发人员。
- 给参数注释
slots
- slots 属性: 可以避免用户频繁的给实例对象动态地添加属性或方法
class Student(object):
pass
s = Student()
s.name = 'Michael' # 动态给实例绑定一个属性
print(s.name)
#Michael
#可以给实例动态绑定一个方法
def set_age(self, age): # 定义一个函数作为实例方法(注意,带上了self)
self.age = age
from types import MethodType
s.set_age = MethodType(set_age, s) # 给实例s绑定set_age()方法
s.set_age(25) # 调用实例方法
s.age # 测试结果
- 给一个实例绑定的方法,对另一个实例是不起作用的
s2 = Student() # 创建新的实例s2
s2.set_age(25) # s2调用方法set_age,报错
- 若要给所有实例绑定方法,可以给class绑定方法
def set_score(self, score):
self.score = score
Student.set_score = set_score #给class绑定方法
#Student.set_age = MethodType(set_age, Student) 也可以
s2.set_score(99) #此时s2可以调用新绑定的方法和属性
s2.score
#99
- 如果要限制Student类中的实例属性,比如只允许实例添加name和age属性时,在定义class时,定义__slots__
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student() # 创建新的实例
s.name = ‘Michael’ # 绑定属性‘name’
s.age = 25 # 绑定属性‘age’
s.score = 99
# 绑定属性‘score’, 报错! score不在__slots__里
- __slots__注意事项
- __slots__定义的属性仅对当前类实例起作用,对继承的子类不起作用
#GraduateStudent 继承Student
class GraduateStudent(Student):
pass
g = GraduateStudent()
g.score = 9999
#Student的子类实例仍然能够动态绑定score
- 除非在子类中也定义__slots__,这样,子类实例允许定义的属性就是自身的__slots__加上父类的__slots__
- slots 只能限制为实例对象动态添加属性和方法,而无法限制动态地为类添加属性和方法。
class Student(object):
__slots__ = ('name', 'age') # 用tuple定义允许绑定的属性名称
s = Student() # 创建新的实例
s.score = 99 # 绑定属性‘score’, 报错! score不在__slots__里
Student.score = 111
#__slot__限制不住为Student类动态添加一个属性或方法
- __slots__的好处
装饰器
- 在代码运行期间动态的给函数或类增加功能的方式,称之为“装饰器”(Decorator)
#定义一个函数now
def now():
print('2015-3-25’)
#功能太简单,想事后给它增加一些功能, 比如,打印日志的功能
#定义一个日志函数log,接受一个函数对象func,打印”call func._name__”,并返回一个函数调用
def log(func):
def wrapper(*args, **kw):
print('call %s():' % func.__name__)#打印日志
return func(*args, **kw)#调用传入的func函数并返回
return wrapper
2. 前面的写法很复杂,有了装饰器之后,用Python的@语法(语法糖),把decorator置于函数的定义处。now函数就被log装饰了,功能也增强了,具备了log函数的功能。
@log
def now():
print('2015-3-25’)
now()
#call now():
#2015-3-25
- 把@log放到now()函数的定义处,相当于执行了语句:
now = log(now)
装饰器 @property
- “实例对象.属性”的方式访问类中定义的属性,其实是欠妥的,因为它破坏了类的封装原则
◼ 应在类中设置多个getter或setter方法
◼ 通过 ”实例对象.方法” 的方式操作属性
◼ 但这种反复调用getter和setter操作类属性的方式比较麻烦
s = Student()
s.score = 9999 #将属性暴露出去,不安全,无法检查值的合法性
- 同@classmethod, @abstractmethod一样, @property也是一个内置装饰器
◼ 负责把一个类的getter方法伪装成属性调用
◼ @property还会创建@xxx.setter和@xxx.deleter装饰器
- property()内置函数用法
- 效果和使用@property装饰器一样
- 使用格式:
◼ 属性名=property(fget=None, fset=None, fdel=None, doc=None)
◼ fget :指定getter方法,fset :指定setter方法,fdel:指定删除该属性的方法,doc : 文档字符串,用于说明此函数的作用。
命名规则
由于 Python 3 支持 UTF-8 字符集,因此 Python 3 的标识符可以使用 UTF-8 所能表示的多种语言的字符。Python 语言是区分大小写的,因此 abc 和 Abc 是两个不同的标识符。
- 标识符可以由字母、数字、下画线(_)组成,其中数字不能打头。
- 标识符不能是 Python 关键字,但可以包含关键字。
- 标识符不能包含空格。