如何优雅地面向对象
概念
类(Class) :用来描述具有相同属性和方法的对象的集合。它定义了该集合中每个对象所共有的属性和方法。其中的对象被称作类的实例。
实例/对象:通过类定义的初始化方法,赋予具体的值,成为一个"有血有肉的实体"。
实例化:类--->对象 的过程或操作。
类变量:类变量是所有实例公有的变量。类变量定义在类中,但在方法体之外。
实例变量:定义在实例中的变量,只作用于当前实例。
实例方法:至少有一个参数并且以实例对象(self)作为其第一个参数的方法。
静态方法:不需要实例化就可以由类执行的方法。
类方法:类方法是将类本身作为对象进行操作的方法。
封装:将内部实现包裹起来,对外透明,提供api接口进行调用的机制。
继承:即一个派生类(derived class)继承父类(base class)的变量和方法。
多态:根据对象类型的不同以不同的方式进行处理。
类的两种作用
1.属性引用(类名.属性)
class Person: # 定义一个Person类
role = 'person' # 静态属性就是直接在类中定义的变量
def __init__(self,name):
self.name = name # 每一个角色都有自己的昵称;
def walk(self): # 动态属性就是定义在类中的方法
print("person is walking...")
print(Person.role) #查看人的role属性
print(Person.walk) #引用人的走路方法,注意,这里不是在调用
2.实例化:类名加括号,自动触发__init__函数的运行,为每个实例定制自己的特征
class 类名:
类属性 = None
def __init__(self,参数1,参数2):
self.对象属性1 = 参数1
self.对象属性2 = 参数2
def 方法名(self):
pass
实例/对象名 = 类名(参数) # 对象就是实例,代表一个具体的东西
# 类名() : 类名+括号就是实例化一个类,相当于调用了__init__方法
# 括号里传参数,参数不需要传self,其他与init中的形参一一对应
# 结果返回一个对象
实例/对象名.对象属性 # 查看对象的属性
实例/对象名.方法名() # 调用类中的方法
类的属性和方法
类属性/实例属性(也叫类变量、实例变量)
1:实例属性:
最好在__init__(self,...)中初始化
内部调用时都需要加上self.
外部调用时用instancename.propertyname
2:类属性:
在__init__()外初始化
在内部用classname.类属性名调用
外部既可以用classname.类属性名又可以用instancename.类属性名来调用
3:私有属性:
1):单下划线_开头:只是告诉别人这是私有属性,外部依然可以访问更改
2):双下划线__开头:外部不可通过instancename.propertyname来访问或者更改
实际将其转化为了_classname__propertyname
实例方法/静态方法/类方法
实例方法:类的实例方法由实例调用,至少包含一个self参数,且为第一个参数。执行实例方法时,会自动将调用该方法的实例赋值给self。self代表的是类的实例,而非类本身
静态方法:静态方法由类调用,无默认参数。将实例方法参数中的self去掉,然后在方法定义上方加上@staticmethod,就成为静态方法。它属于类,和实例无关。建议只使用类名.静态方法的调用方式。(虽然也可以使用实例名.静态方法的方式调用)
class Foo:
@staticmethod
def static_method():
pass
#调用方法
Foo.static_method()
类方法:类方法由类调用,采用@classmethod装饰,至少传入一个cls(代指类本身,类似self)参数。执行类方法时,自动将调用该方法的类赋值给cls。建议只使用类名.类方法的调用方式。(虽然也可以使用实例名.类方法的方式调用)
class Foo:
@classmethod
def class_method(cls):
pass
Foo.class_method()
综合例子:
class Foo:
def __init__(self, name):
self.name = name
def ord_func(self):
"""定义实例方法,至少有一个self参数 """
print('实例方法')
@classmethod
def class_func(cls):
""" 定义类方法,至少有一个cls参数 """
print('类方法')
@staticmethod
def static_func():
""" 定义静态方法 ,无默认参数"""
print('静态方法')
# 调用实例方法
f = Foo("Jack")
f.ord_func()
Foo.ord_func(f) # 请注意这种调用方式,虽然可行,但建议不要这么做!
# 调用类方法
Foo.class_func()
f.class_func() # 请注意这种调用方式,虽然可行,但建议不要这么做!
# 调用静态方法
Foo.static_func()
f.static_func() # 请注意这种调用方式,虽然可行,但建议不要这么做!
封装、继承、多态
封装
封装是指将数据与具体操作的实现代码放在某个对象内部,使这些代码的实现细节不被外界发现,外界只能通过接口使用该对象,而不能通过任何形式修改对象内部实现,正是由于封装机制,程序在使用某一对象时不需要关心该对象的数据结构细节及实现操作的方法。使用封装能隐藏对象实现细节,使代码更易维护,同时因为不能直接调用、修改对象内部的私有信息,在一定程度上保证了系统安全性。类通过将函数和变量封装在内部,实现了比函数更高一级的封装。
继承
Python3的继承机制
- 子类在调用某个方法或变量的时候,首先在自己内部查找,如果没有找到,则开始根据继承机制在父类里查找。
- 根据父类定义中的顺序,以深度优先的方式逐一查找父类!
继承参数的书写有先后顺序,写在前面的被优先继承。
多态
多态指的是一类事物有多种形态
import abc
class Animal(metaclass=abc.ABCMeta): #同一类事物:动物
@abc.abstractmethod
def talk(self):
pass
class People(Animal): #动物的形态之一:人
def talk(self):
print('say hello')
class Dog(Animal): #动物的形态之二:狗
def talk(self):
print('say wangwang')
class Pig(Animal): #动物的形态之三:猪
def talk(self):
print('say aoao')
peo=People()
dog=Dog()
pig=Pig()
# peo、dog、pig都是动物,只要是动物肯定有talk方法
# 于是我们可以不用考虑它们三者的具体是什么类型,而直接使用
peo.talk()
dog.talk()
pig.talk()
#更进一步,我们可以定义一个统一的接口来使用
def func(obj):
obj.talk() # 调用的逻辑都一样,执行的结果却不一样
talk(peo)
talk(dog)
talk(pig)
特殊成员和魔法方法
__init__ : 构造函数,在生成对象时调用
__del__ : 析构函数,释放对象时使用
__repr__ : 打印,转换
__setitem__ : 按照索引赋值
__getitem__: 按照索引获取值
__len__: 获得长度
__cmp__: 比较运算
__call__: 调用
__add__: 加运算
__sub__: 减运算
__mul__: 乘运算
__div__: 除运算
__mod__: 求余运算
__pow__: 幂
常用如下
1.__getitem__()、__setitem__()、__delitem__()
a = 标识符[] : 执行__getitem__方法
标识符[] = a : 执行__setitem__方法
del 标识符[] : 执行__delitem__方法
class Foo:
def __getitem__(self, key):
print('__getitem__',key)
def __setitem__(self, key, value):
print('__setitem__',key,value)
def __delitem__(self, key):
print('__delitem__',key)
obj = Foo()
result = obj['k1'] # 自动触发执行 __getitem__
obj['k2'] = 'jack' # 自动触发执行 __setitem__
del obj['k1'] # 自动触发执行 __delitem__
2.__str__(),__repr__()改变对象的字符串显示
3.__format__自定制格式化字符串
4.__del__()析构方法,当对象在内存中被释放时,自动触发执行
5.__new__单例模式
6.__call__()对象后面加括号,触发执行
7.__len__() len()时执行
8.__module__ 表示当前操作的对象在属于哪个模块
9.__class__ 表示当前操作的对象属于哪个类
10.__dict__列出类或对象中的所有成员!非常重要和有用的一个属性
11.__iter__这是迭代器方法!列表、字典、元组之所以可以进行for循环,内部定义了 __iter__()这个方法
12.__slots__限制实例可以添加的变量
...
反射
反射(或自省)主要是指程序可以访问、检测、和修改它本身状态或行为的一种能力,在python中,通过字符串的形式操作对象相关的属性,python中的一切事物都是对象,即都可以使用反射。
反射4个内置函数分别为:getattr、hasattr、setattr、delattr 获取成员、检查成员、设置成员、删除成员
hasattr:hasattr(object,name)判断一个对象是否有name属性或者name方法。有就返回True,没有就返回False
getattr:获取对象的属性或者方法,如果存在则打印出来。hasattr和getattr配套使用,需要注意的是,如果返回的是对象的方法,返回出来的是对象的内存地址,如果需要运行这个方法,可以在后面添加一对()
setattr:给对象的属性赋值,若属性不存在,先创建后赋值
delattr:删除该对象指定的一个属性
- isinstance(obj,cls)检查是否obj是否是类 cls 的对象
- issubclass(sub, super)检查sub类是否是 super 类的派生类
其他
关于self
self:在实例化时自动将对象/实例本身传给__init__的第一个参数,你也可以给他起个别的名字。
实例(对象)只有一种作用:属性引用
关于super
如果子类中实现了调用父类的方法:
在类内:super(子类,self).方法名() supper().__init__(参数)
在类外:super(子类名,对象名).方法名()
零碎
getattr(obj,"name") = obj.name
Python内置的@property装饰器就是负责把一个方法变成属性调用
super 是用来解决多重继承问题的,直接用类名调用父类方法在使用单继承的时候没问题,
但是如果使用多继承,会涉及到查找顺序(MRO)、重复调用(钻石继承)等种种问题。