guxh的python笔记五:面向对象
1,面向对象编程思想
类:一类具有相同属性的抽象
属性(静态属性):实例变量、类变量、私有属性
方法(动态属性):构造函数、析构函数(默认就有)、函数、私有函数
对象/实例:类经过实例化后,就是对象/实例
封装 encapsulation:隐藏对象的属性和实现细节,仅对外公开接口
继承 inheritance:类派生子类
多态 polymorphism:一个接口,多种实现
举例:
class Foo: # 类
t = 'd' # 类变量,所有实例共享
def __init__(self, x, y): # 构造函数
self.x = x # 属性,实例变量
self._y = y # 私有属性,实例变量 def _test(self): # 私有方法
return self.x def __del__(self): # 析构函数
pass def run(self):
print(self.x, self._y) f = Foo(1, 2) # 类的实例化
f.run() # 调用实例f的方法run
Foo.run(f) # 调用实例f的方法run
类变量t,可以通过Foo.t, type(self).t, self.__class__.t, @classmethod的cls.t去访问和修改。
如果用self.t虽然也可以访问,但修改类变量时,如果t是不可变参数会创建副本。如果是可变序列则用self.t访问和用类访问一样会就地修改,参考函数的传参。
>>>f.t
d
>>>f.t = 'e'
>>>f.t.
e
>>>Foo.t
d
2,继承
代码重用
基类不应该被单独实例化
2.1,继承的方法
class Human:
pass class Male(Human):
pass
2.2,子类继承父类的构造函数或方法
不会自动获得父类构造函数或方法,直接写会覆盖父类的构造函数或者同名方法。
如果想要获得父类的构造函数或者方法,需要通过继承获得。
可以通过super()或指定父类继承构造函数或者方法:
class Base:
def __init__(self):
print('BASE_init') def say(self):
print('BASE_say') class A(Base):
def __init__(self):
super().__init__() # 通过super()继承构造函数 def say(self):
super().__init__() # 通过supe r()继承方法 class B(Base):
def __init__(self):
Base.__init__(self) # 通过指定父类继承构造函数 def say(self):
Base.__init__(self) # 通过指定父类继承方法
2.3,指定父类多重继承
指定父类多重继承构造函数或者方法,结果相同,本节以构造函数为例。
指定父类多重继承:
class Base:
def __init__(self):
print('BASE') class A(Base):
def __init__(self):
Base.__init__(self)
print('A') class B(Base):
def __init__(self):
Base.__init__(self)
print('B') class C(A, B):
def __init__(self):
A.__init__(self)
B.__init__(self)
print('C')
上述代码中,AB都指定继承Base,C先后指定继承AB。
对C实例化时,先后执行AB的构造函数,再执行C自己的构造函数,但Base的构造函数被执行了2遍。
>>> c = C()
BASE
A
BASE
B
C
2.4,super()多重继承
super()多重继承构造函数或者方法,结果相同,本节以构造函数为例。
每个重新定义过的覆盖方法都使用了super()方法时,继承时会遍历整个MRO列表,并且让每个方法只调用一次。(全有super:遍历)
不符合上面条件时,继承时会按照MRO列表继承,继承时执行广度优先继承,同时不会遍历MRO列表,如果某个类有super()时,会继承该类后面的类的方法。(非全有super:广度优先 + MRO列表super)
2.5实验中,C的MRO列表打印如下:
>>> C.__mro__
(<class '__main__.C'>, <class '__main__.A'>, <class '__main__.B'>, <class '__main__.Base'>, <class 'object'>)
1)super()多重继承 — A和B都使用了super()
class Base:
def __init__(self):
print('BASE') class A(Base):
def __init__(self):
super().__init__()
print('A') class B(Base):
def __init__(self):
super().__init__()
print('B') class C(A, B):
def __init__(self):
super().__init__()
print('C')
C实例化:
>>> c = C()
BASE
B
A
C
Base,B,A,C都得到了遍历执行,并且Base的构造方法只执行了一遍。
2)super()多重继承 — A和B都没有使用super()
class Base:
def __init__(self):
print('BASE') class A(Base):
def __init__(self):
print('A') class B(Base):
def __init__(self):
print('B') class C(A, B):
def __init__(self):
super().__init__()
print('C')
C实例化:
>>> c = C()
A
C
根据MRO列表优先只继承A。
如果A没有构造方法,根据广度优先继承B,输出结果是B,C。
3)super()多重继承 — B有super()
C实例化时,输出结果为AC。同2)super()多重继承 — A和B都没有使用super()
如果A没有构造方法,根据广度优先继承B,输出结果是BASE,B,C。(多个BASE是因为B有super())
4)super()多重继承 — A有super()
class Base:
def __init__(self):
print('BASE') class A(Base):
def __init__(self):
super().__init__()
print('A') class B(Base):
def __init__(self):
print('B') class C(A, B):
def __init__(self):
super().__init__()
print('C')
奇怪的现象出现了:
>>> c = C()
B
A
C
这是因为根据MRO列表,顺序为C/A/B/BASE,A的super()继承的是B的构造函数,并不是BASE的构造函数。
2.5,使用mixin类技术扩展类的功能
mixin类时为了扩展其他类的功能而创建的,不能直接实例化,没有__init__(),也没有实例变量
mixin生效的关键是通过super()把任务转交给MRO中的下一个类。
MRO解析顺序为从左至右(object),所以需要注意继承的次序,不确定的话可以通过print(cls.mro())查看。
mixin类实例一:记录字典赋值过程
class LoggedMappingMixin: __slots__ = () def __getitem__(self, item):
print('Getting ' + str(item))
return super().__getitem__(item) def __setitem__(self, key, value):
print('Setting {} = {}'.format(key, value))
return super().__setitem__(key, value) def __delitem__(self, key):
print('Deleting ' + str(key))
return super().__delitem__(key)
使用mixin类:
class LoggedDict(LoggedMappingMixin, dict): # 需要先继承mixin类
pass >>>LoggedDict.mro() # mixin的super继承的是dict
[<class '__main__.LoggedDict'>, <class '__main__.LoggedMappingMixin'>, <class 'dict'>, <class 'object'>]
>>>d['x'] = 42
Setting x = 42
>>>d['x']
Getting x
42
mixin类实例二:实现key类型限制
class RestrictKeysMixin:
def __init__(self, *args, _restrict_key_type, **kwargs):
self._restrict_key_type = _restrict_key_type
super().__init__(*args, **kwargs) def __setitem__(self, key, value):
if not isinstance(key, self._restrict_key_type):
raise TypeError('Keys must be ' + str(self._restrict_key_type))
super().__setitem__(key, value) class Rdict(RestrictKeysMixin, dict):
pass d = Rdict(_restrict_key_type=str)
d[42] = 10 # TypeError: Keys must be <class 'str'>
3,多态
3.1,实现多态
在父类中的一个方法,输入不同子类的对象时,可以实现不同的效果。
父类:
class Human: def __init__(self, name, age):
self.name = name
self.age = age
子类Male:
class Male(Human): def hobby(self):
print('sports')
子类Female:
class Female(Human): def hobby(self):
print('shopping')
不同子类,hobby输出不同,实现了多态:
m = Male('m', 20)
f = Female('f', 20)
m.hobby() # sports
f.hobby() # shopping
还可以编写一个入口函数,统一输出不同对象的hobby:
def get_hobby(obj):
obj.hobby() get_hobby(m) # sports
get_hobby(f) # shopping
3.2,通过抽象基类强制要求子类必须实现方法
如果想要求子类在继承时必须实现hobby方法,可以对父类进行如下改造:
import abc class Human(abc.ABC): def __init__(self, name, age):
self.name = name
self.age = age @abc.abstractmethod
def hobby(self): # 抽象方法无需实现,直接注释或pass即可
"""必须实现hobby"""
4,内置三大类装饰器
4.1,@staticmethod
如果一个方法在类中定义,但没有用到类中的实例属性或者方法,建议加上staticmethod或者classmethod。
staticmethod:本质上就是放在类中的全局函数(可以通过类或实例调用),和类没关系,完全可以放在类的外面。
class Foo:
def __init__(self, x):
self.x = x @staticmethod
def run():
print('hello world') f = Foo(1)
f.run() # 输出hello world,通过类的实例调用
Foo.run() # 输出hello world,通过类调用
4.2,@classmethod
被@classmethod装饰的函数可以通过类或者类的实例调用。
和staticmethod的区别是可以调用类的属性或方法:
class Foo:
hw = 'hello world' @classmethod
def klass_run(cls):
print(cls.hw) f = Foo()
f.klass_run() # 输出hello world,通过类的实例调用
Foo.klass_run() # 输出hello world,通过类调用
被classmethod装饰的函数第一个参数是类的引用,相当于类的实例self执行type(self),或者self.__class__,或者直接调用类Foo。下面实现了相同的效果:
class Foo:
hw = 'hello world' def run(self): # 下面三个print实现了相同的效果
print(type(self), type(self).hw)
print(self.__class__, self.__class__.hw)
print(Foo, Foo.hw) @classmethod
def klass_run(cls):
print(cls, cls.hw) >>>f = Foo()
>>>f.run()
<class '__main__.Foo'> hello world
<class '__main__.Foo'> hello world
<class '__main__.Foo'> hello world
>>>Foo.klass_run()
<class '__main__.Foo'> hello world
>>>f.klass_run()
<class '__main__.Foo'> hello world
4.3,@property
主要三个功能,详见笔记‘类的属性’:
- 函数变属性
- 属性只读
- 属性管理
5,反射
有时建立完类以后,需要动态操纵属性或方法。
可以对类,或者类的实例进行操作
class Foo:
def __init__(self, x):
self.x = x def run(self):
print('hello world') f = Foo(1)
hasattr:测试类/实例是否有属性/方法
print(hasattr(Foo, 'x')) # False
print(hasattr(f, 'x')) # True
print(hasattr(Foo, 'run')) # True
print(hasattr(f, 'run')) # True
getattr:获取类/实例的属性/方法,如果某个方法保存在字符串中,可以用getattr获取并运行,可以实现相同功能的还有operator.methodcaller()
getattr(f, 'run')() # hello world
print(getattr(f, 'x')) # 1
setattr:设置类/实例的属性/方法
def fun(self):
print('hello shanghai') setattr(Foo, 'fun', fun) # 给Foo类增加fun方法
f.fun()
setattr(f, 'fun', fun) # 给f实例增加fun方法,调用时要把实例传进去
f.fun(f)
setattr(Foo, 'typecode', 'd') # 设置类属性
print(Foo.typecode)
setattr(f, 'name', 'guxh') # 设置类属性
print(f.name)
delattr:删除类/实例的属性/方法
delattr(f, 'x')
print(f.x) # AttributeError
6,构造函数
6.1,简化构造函数
有点类似namedtuple:
class Structure:
_fields = [] def __init__(self, *args):
if len(args) != len(self._fields):
raise TypeError('Excepted {} arguments'.format(len(self._fields))) for name, value in zip(self._fields, args):
setattr(self, name, value) class Stock(Structure):
_fields = ['name', 'shares', 'price'] s = Stock('IBM', 50, 68.6)
print(s.name) # ‘IBM'
print(s.shares) # 50
print(s.price) # 68.6
注意,这里用的是setattr设置属性的键值对,如果有实例字典__dict__无法适应__slots__(元组保存)和描述符(或property)场景。
6.2,使用@classmethod创建备选构造函数
import time class Date: def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day @classmethod
def today(cls):
t = time.localtime()
return cls(t.tm_year, t.tm_mon, t.tm_mday) def __repr__(self):
return 'Date({}, {}, {})'.format(self.year, self.month, self.day) a = Date(2019, 10, 1) # 实例a:使用构造函数
b = Date.today() # 实例b:使用备选构造函数
在__init__中使用if...else...虽然也能创造缺省的实例,列入b=Date()时返回today,但是可读性不行
guxh的python笔记五:面向对象的更多相关文章
- 8.python笔记之面向对象基础
title: 8.Python笔记之面向对象基础 date: 2016-02-21 15:10:35 tags: Python categories: Python --- 面向对象思维导图 (来自1 ...
- guxh的python笔记一:数据类型
1,基本概念 1.1,数据类型 基本数据类型:字符串,数字,布尔等 引用数据类型:相对不可变(元组),可变(列表,字典,集合等) 基本数据类型存放实际值,引用数据类型存放对象的地址(即引用) ==:判 ...
- Python:笔记(3)——面向对象编程
Python:笔记(3)——面向对象编程 类和面向对象编程 1.类的创建 说明:和Java不同的是,我们不需要显示的说明类的字段属性,并且可以在后面动态的添加. 2.构造函数 构造函数的功能毋庸置疑, ...
- guxh的python笔记二:函数基础
1,函数的参数 1.1,查看函数的参数类型 def run(a, *args, b, **kwargs): return a + b 可以通过如下方式查看参数类型: import inspect k ...
- guxh的python笔记三:装饰器
1,函数作用域 这种情况可以顺利执行: total = 0 def run(): print(total) 这种情况会报错: total = 0 def run(): print(total) tot ...
- guxh的python笔记十:包和模块
1,包和模块 包package:本质就是一个文件夹/目录,必须带一个__init.__.py的文件 模块module:.py结尾的python文件 2,导入方法 import pandas, coll ...
- guxh的python笔记四:迭代
1,可迭代对象iterable,迭代器iterator,生成器generator 可迭代对象iterable: 实现__iter__方法的类.__iter__方法返回iterator或者generat ...
- 9.Python笔记之面向对象高级部分
类的成员 类的成员可以分为三大类:字段.方法和属性 注:所有成员中,只有普通字段的内容保存对象中,即:根据此类创建了多少对象,在内存中就有多少个普通字段.而其他的成员,则都是保存在类中,即:无论对象的 ...
- Python笔记之面向对象
1.类和对象 #create a class class fruit: def say(self): print "hello, python" if __name__ == &q ...
随机推荐
- Vue事件总线(eventBus)$on()会多次触发解决办法
项目中使用了事件总线eventBus来进行两个组件间的通信, 使用方法是是建立eventBus.js文件,暴露一个空的Vue实例,如下: import Vue from 'vue'export def ...
- day17:递归函数
1,递归函数是一个函数体系,非常的难 2,练习题一 # 3.用map来处理字符串列表,把列表中所有人都变成sb,比方alex_sb name=['alex','wupeiqi','yuanhao',' ...
- linux 逆向映射机制浅析
2017-05-20 聚会回来一如既往的看了会羽毛球比赛,然后想到前几天和朋友讨论的逆向映射的问题,还是简要总结下,免得以后再忘记了!可是当我添加时间……这就有点尴尬了……520还在写技术博客…… 闲 ...
- CS1704问题汇总
“/”应用程序中的服务器错误. 编译错误 说明: 在编译向该请求提供服务所需资源的过程中出现错误.请检查下列特定错误详细信息并适当地修改源代码. 编译器错误消息: CS1704: 已经导入了具有相同的 ...
- 006-mac下finder操作
1. 在 Finder 窗口显示更多信息 打开任意 Finder 窗口.前往并打开「显示」-「显示路径栏」.「显示」-「显示状态栏」和「显示」-「显示预览」三项. 选择了显示路径栏 路径栏通常是从磁盘 ...
- CMake和Linux编程:find_package的使用
1.第一个CMake例子 在 t1 目录建立 main.c 和 CMakeLists.txt(注意文件名大小写): main.c 文件内容: //main.c #include <stdio.h ...
- python学习之数组二
作用于数组的函数: 通用函数:函数基于元素的,以单元方式作用于数组的,返回的是与原数组具有相同形状的数组. 不通用函数(数组函数):函数能以行或者列的方式作用于整个矩阵:如果没有提供任何参数时,它们将 ...
- 自增ID算法snowflake - C#版
急景流年,铜壶滴漏,时光缱绻如画,岁月如诗如歌.转载一篇博客来慰藉,易逝的韶华. 使用UUID或者GUID产生的ID没有规则 Snowflake算法是Twitter的工程师为实现递增而不重复的ID实现 ...
- 为什么一个java源文件中只能有一个public类
问题:一个".java"源文件中是否可以包括多个类(不是内部类)?有什么限制? 答案:可以有多个类,但只能有一个public的类,并且public的类名必须与文件名相一致.一个文件 ...
- 当SQL Server的实例位于集群的特定节点时,数据库无法远程访问
搭建好了一个集群环境,发现当SQL Server的实例位于集群的其中一个节点时,数据库无法远程访问,报如下错误.但在另一个 节点时,数据库访问正常. 标题: 连接到服务器 -------------- ...