26面向对象1_类方法-静态方法-访问控制-猴子补丁
双塔ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为成都创新互联的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:028-86922220(备注:SSL证书合作)期待与您的合作!
目录
面向对象三要素:...3
类对象及类属性:...4
实例化:...6
instance实例对象:...10
装饰一个类:...13
类方法、静态方法:...14
访问控制:...16
猴子补丁:...19
面向对象
语言的分类:
面向机器:
抽象成机器的指令,机器容易理解;
代表:汇编语言;
面向过程:
流程式,第1步,第2步...;
问题规模小,可以步骤化,按部就班处理;
代表:C语言;
面向对象:
随着计算机需要解决的问题的规模扩大,情况越来越复杂,需要很多人、很多部门协作,面向过程编程不太适合了;
代表:C++、java、python;
什么是面向对象?
一种认识世界,分析世界的方法论,将万事万物抽象为类;
class类,是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合;
用计算机语言来描述类,就是属性和方法的集合;
instance实例,或,object对象,是类的具象,是一个实体;
对于我们每个人这个个体,都是抽象概念人类的不同的实体;
例:
你吃鱼:
你,就是对象;
鱼,也是对象;
吃,就是动作;
你是具体的人,是具体的对象,你属于人类,人类是个抽象的概念,是无数具体的个体的抽象;
鱼也是具体的对象,就是你吃的这一条具体的鱼,这条鱼属于鱼类,是无数的鱼抽象出来的概念;
注:
数据<-->属性;
动作<-->方法;
属性和方法的集合体;
python中函数和方法要加以区分;
属性也决定着方法的多少,有一些方法是为内部属性作操作的,有一些方法是对外的;
项目有多复杂,数据结构就有多复杂;
类是数据和动作,即属性和方法的集合;
抽象?(数据和动作的集合);
具象?
吃,是动作,是操作,也是方法,这个吃是你的动作,也就是人类具有的方法;如果反过来,鱼吃人,吃就是鱼类的动作了;
吃,这个动作,很多动物都具有,人类和鱼类都属于动物类,而动物类是抽象的概念,是动物都有吃的动作,但吃法不同而已;
你驾驶车,这个车是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的,是人类具有的方法;
属性,是对象状态的抽象,用数据结构来描述;
操作(方法),是对象行为的抽象,用操作名和实现该操作的方法来描述;
每个人都有名字、身高、体重等信息,这些信息是个人的属性,但这些信息不能保存在人类中,因为人类是抽象的概念,不能保留具体的值;
而人类的实例,是具体的人,他可以存储这些具体的属性,而且不同人有不同的属性;
哲学:
一切皆对象;
对象是数据(属性)和操作(方法)的封装;
对象是独立的,但对象之间可相互作用;
目前,面向对象是最接近人类认知的编程范式;
UML,unified modeling language,统一建模语言;
面向对象三要素:
1、封装:
组装:将数据和操作组装到一起;
隐藏数据:对外只暴露一些接口,通过接口访问对象;(如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解背后的机动原理);
encapsulation封装:
面向对象的三要素之一;
将数据和操作组织到类中,即属性和方法;
将数据隐藏起来,给使用者提供操作,使用者通过操作就可获取或修改数据,getter,setter;
通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来,保护成员或私有成员;
2、继承:
多复用,继承是为了复用,继承来的就不需要自己写了;
多继承少修改,ocp,open-closed-principle开闭原则,使用继承来改变,来体现个性;
多继承慎用,问题失控,计算机所用技术,简单就是美的;
注:
函数复用,如yield from;
3、多态:
面向对象编程最灵活的地方,动态绑定;
python运行时才绑定(知道)类型;
人类就是封装;
人类继承自动物类,孩子继承父母的特征,分为单一继承、多继承;
多态,继承自动物类的人类,猫类的操作“吃”不同;
其它语言的继承、多态与python不一样,仅封装一样;
在面向对象中,父类、子类通过继承联系在一起,如果可通过一套方法,就可实现不同表现,就是多态;
一个类继承自多个类,就是多继承,它将具有多个类的特征;
python的类:
定义:
class ClassName:
语句块
必须使用class关键字;
类名必须用大驼峰(首字母大写)命名,习惯,不是语法强制;
类定义完成后,就产生了一个类对象,绑定到了ClassName上;
注:
区分:类对象和类的对象;
例:
class MyClass:
'''a example class'''
x = 'abc' #类属性,另有对象属性(类的实例的属性)
def foo(self): #类属性foo(单从标识符讲,foo是类属性),同时也是方法,self必须作为第1个参数
print(self.x)
return 'My class'
print(MyClass)
print(type(MyClass))
print(MyClass.__name__)
print(MyClass.x) #不是内存地址,高级语言对能简化的就简化了
print(MyClass.foo) #内存地址
print(MyClass().foo())
print(MyClass.__doc__)
输出:
MyClass
abc
abc
My class
a example class
类对象及类属性:
类对象,类的定义就会生成一个类对象;
类的标识符:
类的属性,类中定义的变量和类中定义的方法都是类的属性;
类变量,上例中x是MyClass的变量;
MyClass中,x、foo都是类的属性,__doc__也是类的属性;
foo方法是类的属性,如同吃是人类的方法,但是每一个具体的人才能吃东西,也就是说吃是人类的实例才能调用的方法;
foo是method方法对象,不是普通的function函数对象,它必须至少有一个参数,且第一个参数必须是self(self可换名字),这个参数位置就留给了self;
self指代当前实例本身,self这个名字只是一个惯例,它可以修改,但请不要修改,否则影响代码的可读性;
注:
inspect中,isfunction()、ismethod();
def bar():
class MyClass:
example = bar #语法虽允许,但不要这样写,破坏了封装,不符合编程规范,方法就写在类内部
例:
class MyClass:
'''a example class'''
x = 'abc'
def foo(self):
# print(self.x)
print(self)
# return 'My class'
# print(MyClass)
# print(type(MyClass))
# print(MyClass.__name__)
# print(MyClass.x)
# print(MyClass.foo)
print(MyClass().foo())
# print(MyClass.__doc__)
print(MyClass.foo(1)) #不判断self的类型,1、None
mycls = MyClass() #实例化、初始化
print(mycls.foo()) #对象调用方法,相当于悄悄的把mycls放入foo(mycls)中
print(mycls.x) #实例可拿走类属性
print(mycls.foo) #对象绑定方法
输出:
<__main__.MyClass object at 0x7f338d4ff438>
None
1
None
<__main__.MyClass object at 0x7f338d4ff438>
None
abc
例:
class MyClass:
'''a example class'''
x = 'abc'
def foo(self): #self可改名
# print(self.x)
# print(self)
print(id(self))
# return 'My class'
# return self
mycls = MyClass()
print(mycls.foo())
# print(mycls.x)
# print(mycls.foo)
print(id(mycls))
输出:
140426199077888
None
140426199077888
常用:
print(MyClass.x)
mycls = MyClass()
print(mycls.x)
print(mycls.foo())
实例化:
mycls = MyClass(),
在类对象名称后面加(),就调用类的实例化方法,完成实例化;
实例化,就真正创建一个该类的对象(实例),如人类的实例tom,jerry;
实例化后,获得的实例,是不同的实例,即使是使用同样的参数实例化,也得到不一样的对象;
python类实例化后,会自动调用__init__(self)方法,这个方法第一个参数必须留给self,其它参数随意;
__init__(self)方法:
MyClass实际上调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用;
作用:对实例进行初始化;
__init__(self)方法与其它方法不一样,返回值只能是None,一般不写返回值,如果写return只能两种:return和return None,其它形式一律报错;
初始化函数可以多个参数,第一个参数必须self;
初始化函数也称构造器,构造方法,仅初始化,构造实例是__new__方法;
另,__new__(cls,*args,**kwargs),用于构建实例,极少用,类方法;
__init__(self)方法,默认的语句是:
def __init__(self):
pass
例:
class MyClass:
'''this is a example class'''
x = 123
def __init__(self):
print('init') #自定义初始化
def foo(self):
return 'foo = {}'.format(self.x)
a = MyClass()
print(a.foo())
输出:
init
foo = 123
例:
class MyClass:
'''this is a example class'''
def __init__(self):
print('self in init = {}'.format(id(self))) #self就是调用者,即实例对象c
c = MyClass()
print('c = {}'.format(id(c)))
输出:
self in init = 140190037407448
c = 140190037407448
例:
class Person:
x = 'abc'
def __init__(self,name,age=18):
self.name = name #实例属性,对象的属性
self.age = age
def showage(self):
print('{} is {}'.format(self.name,self.age))
tom = Person('tom')
jerry = Person('jerry',20)
print(tom.name,tom.age)
print(jerry.name,jerry.age)
print(tom.x,jerry.x)
输出:
tom 18
jerry 20
abc abc
例:
class Person:
x = 'abc'
def __init__(self,name,age=18):
self.name = name
self.age = age
def showage(self):
print('{} is {}'.format(self.name,self.age))
tom = Person('tom')
# jerry = Person('jerry',20)
# print(tom.name,tom.age)
# print(jerry.name,jerry.age)
# print(tom.x,jerry.x)
# print(tom == jerry)
jerry = Person('tom')
print(tom.name,tom.age)
print(jerry.name,jerry.age)
print(tom == jerry) #tom和jerry是不同的个体,尽管初始化时参数一样
print(tom is jerry)
输出:
tom 18
tom 18
False
False
例:
class Person:
x = 'abc'
def __init__(self,name,age=18):
self.name = name
# self.age = age
self.y = age
def showage(self,x,y): #showage中的形参y和self.y不一样,self.y是实例在外部使用时用的,y是形参
print('{} is {}. {} {}'.format(self.name,self.y,x,y))
self.y = x
Person.x = x #类中的所有方法,包括特殊__init__(self)方法,都可对类属性或实例属性进行修改
tom = Person('tom')
jerry = Person('jerry',20)
# print(tom.name,tom.age)
# print(jerry.name,jerry.age)
# print(tom.x,jerry.x)
# print(tom == jerry)
# jerry = Person('tom')
# print(tom.name,tom.age)
# print(jerry.name,jerry.age)
# print(tom == jerry)
# print(tom is jerry)
print(tom.y,jerry.y)
tom.showage(100,'a')
jerry.showage(200,'b')
print(tom.y,jerry.y)
print(Person.x)
print(x) #当前作用域中没有x,也说明x在Person类中封装着,要访问x前面要加限定,如Person.x
输出:
18 20
Traceback (most recent call last):
tom is 18. 100 a
jerry is 20. 200 b
100 200
200
File "/home/python/magedu/projects/cmdb/example_class_Person.py", line 29, in
print(x)
NameError: name 'x' is not defined
instance实例对象:
类初始化后一定会获得一个对象,就是实例对象;
tom、jerry就是Person类的实例;
__init__方法的第一个参数self,就是指代某一个实例;
类实例化出一个实例对象,实例对象会绑定方法,调用方法时采用jerry.showage()的方式;
在定义showage(self)时,不能少了self,这个self就是jerry,python会把方法的调用者作为第一个参数self的实参传入;
self.name就是jerry对象的name,name是保存在了jerry对象上,而不是Person类上,所以称为实例变量;
实例变量是每一个实例自己的变量,是自己独有的;
类变量是类的变量,是类的所有实例共享的属性和方法;
类属性保存在类的__dict__中;
实例属性保存在实例的__dict__中;
如果从实例访问类的属性,需要借助__class__找到所属的类;
特殊属性:
__name__,对象名;
__class__,对象的类型,python3中__class__和type()结果一样;
__dict__,对象的属性的字典;
__qualname__,类的限定名;
总结:
是类的,也是这个类所有实例的,其实例都可以访问到类中定义的属性和方法;是类的,就是大家的;
是实例的,就是这个实例自己的,通过类访问不到;是实例的,就是个体的;
类变量,是属于类的变量,这个类的所有实例可以共享这个变量;
实例可以动态的给自己添加或删除一个属性,实例.__dict__['变量名']和实例.变量名都可访问到;也可动态添加类方法,这些会破坏封装,不要这么做,虽语法允许,但从设计角度不好;
实例的变量会隐藏与其同名的类的变量(遮盖),或者说是覆盖了类变量;
实例属性的查找顺序:
实例使用.点来访问属性,会先找自己的__dict__;
如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找;
如果实例使用__dict__['变量名']来访问(直接翻字典),将不会按照上面的查找顺序找变量;
一般类变量使用全大写来命名;
tom.__class__.__dict__等价于Person.__dict__;
例:
class Person:
age = 18 #类变量,通常是一常量,很少用
def __init__(self,name):
self.name = name #编程中,大量用的是实例变量,而不是类变量
# tom = Person('tom',20) #X
tom = Person('tom') #初始化、实例化
jerry = Person('jerry')
print(tom.name,tom.age)
print(jerry.name,jerry.age)
print(Person.age)
# print(Person.name) #X
Person.age = 30 #在外部修改类属性
print(tom.age,jerry.age,Person.age)
print(Person.__dict__) #__weakref__弱引用,用.点查找属性
print(tom.__dict__) #每个对象保存着自己的属性,所有对象的操作方法是一样的,无非是数据不一样(传入的参数不一样)
print(jerry.__dict__)
print(tom.__dict__['name'])
print(sorted(Person.__dict__.items()),end='\n')
print(sorted(tom.__dict__.items()),end='\n')
# print(tom.__qualname__) #某一对象并不拥有所有特殊属性
print(tom.__class__.__qualname__,jerry.__class__.__qualname__)
print(isinstance(jerry,tom.__class__))
print(int.__class__)
print(Person.__class__)
print(isinstance(tom,int.__class__))
输出:
tom 18
jerry 18
18
30 30 30
{'__module__': '__main__', 'age': 30, '__init__':
{'name': 'tom'}
{'name': 'jerry'}
tom
[('__dict__',
[('name', 'tom')]
Person Person
True
False
例:
class Person:
age = 3
height = 170
def __init__(self,name,age=18): #方法中的第一个参数self,表示bound了对象(实例)
self.name = name
self.age = age
tom = Person('tom')
jerry = Person('jerry',20)
Person.age = 30
print(Person.age,tom.age,jerry.age)
print(Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
print(Person.height,tom.height,jerry.height)
Person.height += 20
print(Person.height,tom.height,jerry.height)
print(Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
tom.height = 168
print(Person.height,tom.height,jerry.height)
print(Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
jerry.height += 20
print(Person.height,tom.height,jerry.height)
print(Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
Person.weight = 70 #动态添加(删除)属性,灵活之处,也可动态添加方法,这些会破坏封装,不要这么做
print(Person.weight,tom.weight,jerry.weight) #先找自己的__dict__,找不到再通过__class__找类中的__dict__,类中也没有抛异常KeyError
print(tom.__dict__['weight']) #X,KeyError
输出:
30 18 20
{'__module__': '__main__', 'age': 30, 'height': 170, '__init__':
{'name': 'tom', 'age': 18}
{'name': 'jerry', 'age': 20}
170 170 170
190 190 190
{'__module__': '__main__', 'age': 30, 'height': 190, '__init__':
{'name': 'tom', 'age': 18}
{'name': 'jerry', 'age': 20}
190 168 190
{'__module__': '__main__', 'age': 30, 'height': 190, '__init__':
{'name': 'tom', 'age': 18, 'height': 168}
{'name': 'jerry', 'age': 20}
190 168 210
{'__module__': '__main__', 'age': 30, 'height': 190, '__init__':
{'name': 'tom', 'age': 18, 'height': 168}
{'name': 'jerry', 'age': 20, 'height': 210}
70 70 70
Traceback (most recent call last):
File "/home/python/magedu/projects/cmdb/example_class_var.py", line 29, in
print(tom.__dict__['weight'])
KeyError: 'weight'
装饰一个类:
不是类装饰器;
需求:为一个类通过装饰,增加一些类属性;
用于老项目,不动类定义,通过装饰器动态的添加类属性;
def setnameproperty(name):
def wrapper(cls):
cls.NAME = name
return cls
return wrapper
@setnameproperty('MYCLASS') #MyClass = setnameproperty('MYCLASS')(MyClass)
class MyClass:
pass
print(MyClass.__dict__)
输出:
{'__module__': '__main__', '__dict__':
类方法、静态方法:
类方法,用装饰器@classmethod,在定义时,第1个参数留给类本身,如def clsmtd(cls),与类bound(类似对象方法,第1个参数self,与实例bound);
类中普通方法,def bar(),虽语法允许,不推荐使用,若非要不带参数,用静态方法替代;
静态方法,用装饰器@staticmethod;
python中类方法,相当于其它语言的静态方法;java、c++中的静态方法指的是python中的类方法;
类方法,如datetime.datetime(...)创建时间对象;
静态方法,从概念上归类管辖,实际使用可以与类没有关系,很少用;
__init__()等方法,这些本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,即类必须实例化后,由实例来调用这个方法;
普通方法:
MyClass.bar(),先查看MyClass.__dict__,这个方法只是被MyClass这个名词空间管理的一个普通的方法,bar是MyClass的一个属性而已,由于bar在定义时没有指定self,这样即没有完成与实例对象的绑定,不能用a.bar()或MyClass().bar()调用,这种虽语法对,但没人这么用,禁止这样定义def bar();
类方法:
在类定义中,用@classmethod装饰器修饰的方法;
pycharm中,定义时会自动补全cls参数,必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即对象自身;
cls这个标识符可以是任意合法名称,但是为了易读,请不要修改;
通过cls可直接操作类的属性,无法通过cls操作类的实例;
类方法,类似c++、java中的静态方法;
静态方法:
在类定义中用@staticmethod装饰器修饰的方法;
调用时,不会隐式的传入参数;
静态方法,只是表明这个方法属于类这个名词空间,函数归在一起,方便组织管理;
注:
实例可调用类中定义的方法(包括类方法、静态方法),静态方法和类方法需要找到实例的类;
实例不可调用类中的普通方法,如a.bar(),解释器会自动将a传到bar(a)内,而bar在定义时没有self,即没有绑定实例;
例:
class MyClass:
XXX = 'xxx'
def __init__(self):
print('init')
def foo(self):
print('foo')
def bar():
print('bar')
@classmethod
def clsmtd(cls): #类方法,cls为类本身,或是实例的类
print('{},xxx={}'.format(cls.__name__,cls.XXX))
@staticmethod
def staticmtd():
# def staticmtd(x):
print('static')
a = MyClass()
a.foo()
MyClass.bar() #V,可以这样用,bar是在MyClass类下定义的
# a.bar() #X,foo和bar都是function,bar中形参没有self,为类中的普通函数,这样调用会自动把a作为形参传入到bar(a)会出错
print(MyClass.__dict__)
MyClass.clsmtd() #同a.foo(),会将类本身MyClass传入clsmtd(MyClass)类方法的第一个参数
a.clsmtd() #是类的,就是大家的,所以实例可以用,相当于a.__class__.clsmtd()
MyClass.staticmtd() #V
a.staticmtd() #是类的,就是大家的
# a.staticmtd(4)
输出:
init
foo
bar
{'__module__': '__main__', 'XXX': 'xxx', '__init__':
MyClass,xxx=xxx
MyClass,xxx=xxx
static
static
例:
class Person:
@classmethod
def cls_method(cls):
print('class = {0.__name__} ({0})'.format(cls))
cls.HEIGHT = 170 #可操作类的属性,不能操作实例的属性
@staticmethod
def static_method():
print(Person.HEIGHT)
Person.cls_method()
print(Person.__dict__)
Person.static_method()
输出:
class = Person (
{'__module__': '__main__', 'cls_method':
170
访问控制:
private,私有属性,双下划线开头的属性名;
私有变量的本质:类定义的时候,如果声明一个实例变量使用双下划线开头,python解释器会将其改名,转换名称为_类名__变更名,所以在外部用原来的名字(__age)访问不到了,但仍可通过__dict__访问或修改;其它语言不是这样,是确确实实看不到,python中仍然可在外部通过_Person__age访问或修改;
保护变量:
在变更名前加一个下划线,查看__dict__,解释器不做任何特殊处理,这只是开发者共同的约定,看见这种变量,就如同私有变量,不要直接使用;
私有方法:
双下划线开头的方法,解释器会改名,改名策略和私有变量相同,_类名__方法名;
单下划线的方法,只是开发者之间的约定,解释器不作任何改变;
方法变量都在类的__dict__中可找到;
私有成员的总结:
在python中使用_或__来标识一个成员被保护或被私有化隐藏起来,但是不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员,python中没有绝对的安全的保护成员或私有成员;
因此前导的下划线只是一种警告或提醒,请遵守这个约定,除非真有必要,不要修改或者使用保护成员或私有成员;
以上是在类中使用_或__,如果是在函数中使用则不起作用;
public、protect、private,其它语言的访问控制,而python中的访问控制只是假象;
例:
class Person:
def __init__(self,name,age=18):
self.name = name
self.age = age
def growup(self,incr):
if 0 < incr < 150: #控制逻辑
self.age += incr
tom = Person('tom')
tom.growup(20)
tom.age = 160 #未用私有属性,会绕过控制逻辑,外部可见,外部可定义和修改,越过我们定义的范围
print(tom.age)
输出:
160
例:
class Person:
def __init__(self,name,age=18):
# self.name = name
self._name = name
# self.age = age
self.__age = age #对象字典中的key表现为_Person__age,自动对应的
def growup(self,incr):
if 0 < incr < 150:
# self.age += incr
self.__age += incr
def getage(self):
return self.__age
tom = Person('tom')
tom.growup(2)
# tom.age = 160
# print(tom.age)
# print(tom.__age) #X,不能直接访问私有属性,报AttributeError: 'Person' object has no attribute '__age'
print(tom.getage()) #通过方法访问私有属性,没多大用处
print(Person.__dict__)
print(tom.__dict__)
tom._Person__age = 200 #破坏封装
print(tom.getage())
tom.__age = 300 #破坏封装,重新定义的__age,与对象字典的_Person__age不是同一个key,不会被覆盖
print(tom.getage())
print(tom.__dict__)
print(tom.__age)
输出:
20
{'__module__': '__main__', '__init__':
{'_name': 'tom', '_Person__age': 20}
200
200
{'_name': 'tom', '_Person__age': 200, '__age': 300}
300
tom
{'_name': 'tom', '_Person__age': 200, '__age': 300}
猴子补丁:
可通过修改或者替换类的成员;
使用者调用的方式没有改变,但类提供的功能可能已经改变了;
适用于上线后的紧急或临时修复上,一般下次变更时会合并到代码中;
monkey patch,猴子补丁,在运行时对属性进行动态替换;
黑魔法,慎用;
类方法中的名字,get*读,set*写,惯例;
例:
使用monkey patch,替换getscore方法,返回模拟的数据;
test2.py
class Person:
def __init__(self,chinese,english,history):
self.chinese = chinese
self.english = english
self.history = history
def getscore(self):
return self.chinese,self.english,self.history
test3.py
def getscore(self):
return dict(chinese=self.chinese,english=self.english,history=self.history)
test1.py
from test2 import Person
from test3 import getscore
def monkeypatch5Person():
Person.getscore = getscore
stu1 = Person(80,90,88)
print(stu1.getscore())
monkeypatch5Person()
stu2 = Person(70,80,90)
print(stu2.getscore())
输出:
(80, 90, 88)
{'chinese': 70, 'english': 80, 'history': 90}
文章标题:26面向对象1_类方法-静态方法-访问控制-猴子补丁
文章网址:http://myzitong.com/article/ijgopj.html