python面向对象
编程语言的分类
编程语言的分类大体可分为三类,面向对象、面向过程、面向对象oop。
面向机器:它是抽象成机器指令,这样机器容易理解,代表为汇编语言。
面向过程:做一件事情,排出一个步骤,第一步做什么,第二步做什么,如果出现情况A,做什么处理,如果出现了情况B,做什么处理。它特点是问题规模小,可以步骤化,按部就班的处理。代表语言C语言。
面向对象oop:随着计算机需要解决的问题的规模不断扩大,情况越来越复杂,需要很多人,很多部门协作,面向过程编程不太适合了。这个面对相对编程就出来了。代表语言:c++,Java,Python等。
面向对象
面向对象适合大规模软件,是一种认识世界,分析世界的方法论,它将万事万物抽象为类。
类class
类是抽象的概念,是万事万物的抽象,是一类事物的共同特征的集合。用计算机语言来描述类,就是属性和方法的集合。
对象instance、object
对事是类的具象,是一个实体,对于我们每个人这个个体,都是抽象概念人类的不同实体。
比如:你吃鱼,你就是对象,鱼也是对象,而吃是动作。你是具体的人,是具体的对象,你属于人类,人类是个抽象的概念,是无数具体的个体的抽象。鱼也是具体的对象,就是你吃的这一条具体的鱼,这条鱼属于鱼类,是无数的鱼抽象出来的概念。吃是动作,也是操作,也是方法,这个吃是你的动作,也就是人类具有的方法,如果反过来,鱼吃人,吃就是鱼类的动作了。
吃这个动作,很多动物都具有的动作,人类和鱼类都属于动物类,而动物类是抽象的概念,是动物都有吃的动作,但是吃法不同而已。
你驾驶车,这个车也是车类的具体的对象(实例),驾驶这个动作是鱼类不具有的,是人类具有的方法。
属性:它是对象状态的抽象,用数据结构来描述。
操作:它是对象行为的抽象,用操作名和实现该操作的方法来描述。
每个人都有名字、身高、体重等信息,这些信息是个人的属性,但是这些信息不能保存在人类中,因为它是抽象的概念,不能保留具体的值。
而人类的实例,是具体的人,他可以存储这些具体的属性,而且可以不同人有不同的属性。
哲学:
一切皆对象
对象是数据和操作的封装
对事是独立的,但是对象之间可以相互作用。(人吃鱼)目前oop是最接近人类认知的编程范式。
面向对象3要素
封装
- 组装:将数据和操作封装到一起。
- 隐藏数据:对外只暴露一些借口,通过接口访问对象,比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解背后的机动原理。
继承
- 多复用,继承来的就不用自己写了。
- 多继承少修改,OCP(open-closed principle),使用继承来改变,来体现个性。
多态:面向对象编程最灵活的地方,动态绑定。(Python本身就是多态,它面向对象特别彻底)
人类就是封装,人类继承自动物类,孩子继承父母特征,分为单一继承、多继承。多态:继承自动物类的人类、猫类的操作“吃”不同。
Python类
定义
class classname: 语句块
- 必须使用class关键字。
- 类名必须是用大驼峰命名。
- 类定义完成后,就产生了一个类对象(类本身也是一个对象),绑定到了标识符ClassName上。
class MyClass: """A example class""" x = "abc"#类属性 def foo(self):#类属性foo,也是方法 return "My class" print(MyClass.x) print(MyClass.foo) print(MyClass.__doc__)
print(type(MyClass)) 结果为: abc <function MyClass.foo at 0x00000000059C2488> A example class
<class 'type'>
类对象及类属性
类对象:类的定义就会生成一个类对象。
类的属性:类定义中的变量和类中定义的方法都是类的属性。
类变量:上面的例子中的x就是MyClass的变量。
上面的例子中,x、fool都是类的属性,__doc__也是类的属性。
foo方法是类的属性,如同吃是人类的方法,但是每一个具体的人才能吃东西,也就是说吃是人的实例才能调用的方法。
foo是method方法,不是普通的函数对象function了,它一般要求有一个参数,第一个参数可以是self(self只是个惯用标识符,可以换名字),这个参数位置就留给了self。
self指代的是当前实例本身。
实例化
a = MyClass()#实例化,初始化
type(a)
结果为:
__main__.MyClass
class MyClass: """A example class""" x = "abc"#类属性 def foo(self):#类属性foo,也是方法 return self.x print(MyClass) print(MyClass.__name__) print(MyClass.x) print(MyClass.foo) print(MyClass.__doc__) print(type(MyClass)) print("```````````````````````") a = MyClass() print(a.foo()) print(a.x) print(a.foo) 结果为: <class '__main__.MyClass'> MyClass abc <function MyClass.foo at 0x03B60618> A example class <class 'type'> ``````````````````````` abc abc <bound method MyClass.foo of <__main__.MyClass object at 0x03B6F290>>
class MyClass: """A example class""" x = "abc"#类属性 def foo(self):#类属性foo,也是方法 print(id(self)) return self print(MyClass) print(MyClass.__name__) print(MyClass.x) print(MyClass.foo) print(MyClass.__doc__) print(type(MyClass)) print("```````````````````````") a = MyClass() print(a.foo()) print(a.x) print(a.foo) print("=============") print(a.foo()) print(id(a)) 结果为: <class '__main__.MyClass'> MyClass abc <function MyClass.foo at 0x03B60660> A example class <class 'type'> ``````````````````````` 62323696 <__main__.MyClass object at 0x03B6FBF0> abc <bound method MyClass.foo of <__main__.MyClass object at 0x03B6FBF0>> ============= 62323696 <__main__.MyClass object at 0x03B6FBF0> 62323696
使用上面的语法,在类对象名称后面加上一个括号, 就调用类的实例化方法,完成实例化,实例化就是真正创建一个该类的对象(实例),比如:
tom = Person()
jerry = Person()
上面的tom、Jerry都是person类的实例,通过实例化后生成了2个实例。每次实例化后获得的实例,是不同的实例,即使是使用相同的参数实例化,也得到不一样的对象。Python类实例话后,会自动调用__init__方法,这个方法第一个参数必须留给self,其他参数随意。
class MyClass: """A example class""" x = "abc"#类属性 def foo(self):#类属性foo,也是方法,self跟实例有关, print(id(self)) return self print(MyClass.x) a = MyClass() print(a.foo())#类的标识符跟实例a进行了绑定 print(a.x) 结果为: abc 62323760 <__main__.MyClass object at 0x03B6FC30> abc
__init__方法
MyClass()实际调用的是__init__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用。之前会有__new__方法。
作用:对实例进行初始化。
class MyClass: def __init__(self): print("init") print(MyClass)#不会调用 print(MyClass())#调用__init__ a = MyClass()#调用__init__ 结果为: <class '__main__.MyClass'> init <__main__.MyClass object at 0x00000000059D1160> init
初始化函数可以为多个参数,但是第一个位置必须是留给self的,例如__init__(self,name,age)
class Person: x = "abc"#类属性 def __init__(self,name): self.name = name #实例属性 a = Person("ton") b = Person("ton") print(a.name,b.name) print(a.x,b.x) print(a==b) print(a is b) 结果为: ton ton abc abc False False
实例化后就算名字相同,也是两个不同的实体。
class Person: def __init__(self,name,age): self.name = name self.age = age def showage(self): print("{} is {}".format(self.name,self.age)) tom = Person("Tom",20)#实例化 jerry = Person("Je",25) print(tom.name,jerry.name) jerry.age+=1 print(jerry.age) jerry.showage() 结果为: Tom Je 26 Je is 26
注意:__init__()方法不能有返回值,也就是只能是none。
class Person: x = "abc" def __init__(self,name,age = 18): self.name = name self.y = age#y就是一个标识符而已,可以任意取名 a = Person("ton") b = Person("jerry",20) print(a.name,b.name) print(a.x,b.x) print(a.y,b.y) 结果为: ton jerry abc abc 18 20
class Person: x = "abc" def __init__(self,name,age = 18): self.name = name self.y = age#y就是一个标识符而已,可以任意取名 def show(self,x,y): print(self.name,x,y)#这个x,y和上面的self.y不是同一个,这个相当于是形参,要后面传进来。 a = Person("ton") b = Person("jerry",20) print(a.name,b.name) print(a.x,b.x) print(a.y,b.y) a.show(100,"a") 结果为: ton jerry abc abc 18 20 ton 100 a
class Person: x = "abc" def __init__(self,name,age = 18): self.name = name self.y = age#y就是一个标识符而已,可以任意取名 def show(self,x,y): print(self.name,x,y)#这个x,y和上面的self.y不是同一个,这个相当于是形参,要后面传进来。 self.y = x Person.x = x a = Person("ton") b = Person("jerry",20) print(a.name,b.name) print(a.x,b.x) print(a.y,b.y) a.show(100,"a") print(a.y) print(Person.x) 结果为: ton jerry abc abc 18 20 ton 100 a 100 100
实例对象instance
类实例化后一定会获得一个对象,就是实例对象,上面例子中的tom、jerry就是person类的实例。
__init__方法的第一个参数self就是指代某一个实例。
类实例化后,得到一个实例对象,实例对象会绑定方法,调用方法时采用jerry.showage()的方式,但是这个函数签名是showage(self),少传一个参数吗?
这个self就是jerry,python会把方法的调用者作为第一个参数self的实参传入,self.name就是jerry对象的name,name是保存在了jerry对象上,而不是person类上,所以,称为实例变量。
class MyClass: def __init__(self): print("self in init = {} ".format(id(self))) c = MyClass()#会调用__init__方法 print("c = {}".format(id(c))) 结果为: self in init = 94250320 c = 94250320
上例说明,self就是调用者,就是c对应的实例对象。self这个名字只是一个惯例,它可以修改,但是请不要修改,不然会影响代码的可读性。
看打印的结果,思考一下执行的顺序,为什么?
class MyClass: """A example class""" x = "abc"#类属性 def __init__(self): print("init") def foo(self):#类属性foo,也是方法 return "foo={}".format(self.x) a = MyClass() print(a.foo()) 结果为: init foo=abc
实例化的时候,会调用__init__方法初始化。如果没有会隐式的调用。
实例变量和类变量
class Person: age = 3 def __init__(self,name): self.name = name def showage(self): print("{} is {}".format(self.name,self.age)) tom = Person("Tom")#实例化,初始化 jerry = Person("Jerry") print(tom.name,tom.age) print(jerry.name,jerry.age) print(Person.age) Person.age= 30 print(Person.age,jerry.age,jerry.age) 结果为: Tom 3 Jerry 3 3 30 30 30
实例变量是每一个实例自己的变量,是自己独有的, 而类变量是类的变量,是类的所有实例共享的属性和方法。
特殊属性:
__name__:对象名
__class__:对象的类型。和type看的内容是一样。
__dict__:对象属性的字典。
__qualname__:类的限定名(自己定义的类的实例没有这个属性)
注意:Python中每一种对象都拥有不同的属性,函数、类都是对象,类的实例也是对象。
class Person: x = "abc" def __init__(self,name,age = 18): self.name = name self.y = age#y就是一个标识符而已,可以任意取名 def show(self,x,y): print(self.name,x,y)#这个x,y和上面的self.y不是同一个,这个相当于是形参,要后面传进来。 self.y = x Person.x = x a = Person("ton") b = Person("jerry",20) print(a.__class__,b.__class__) print(a.__class__.__qualname__,a.__class__.__name__) print(Person.__dict__) print(a.__dict__) print(b.__dict__) 结果为: <class '__main__.Person'> <class '__main__.Person'> Person Person {'__module__': '__main__', 'x': 'abc', '__init__': <function Person.__init__ at 0x03B604B0>, 'show': <function Person.show at 0x03B604F8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None} {'name': 'ton', 'y': 18} {'name': 'jerry', 'y': 20}
class Person: age = 3 def __init__(self,name): self.name = name def showage(self): print("{} is {}".format(self.name,self.age)) print("---------class----------------") print(Person.__class__) print(sorted(Person.__dict__.items()),end = "\n\n")#属性字典 tom = Person("Tom")#实例化,初始化 print("-------instance tom--------") print(tom.__class__) print(sorted(tom.__dict__.items()),end = "\n\n") print("-------tom's class--------") print(tom.__class__.__name__) print(sorted(tom.__class__.__dict__.items()),end = "\n\n") 结果为: ---------class---------------- <class 'type'> [('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x0000000005A6DE18>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3), ('showage', <function Person.showage at 0x0000000005A6DD90>)] -------instance tom-------- <class '__main__.Person'> [('name', 'Tom')] -------tom's class-------- Person [('__dict__', <attribute '__dict__' of 'Person' objects>), ('__doc__', None), ('__init__', <function Person.__init__ at 0x0000000005A6DE18>), ('__module__', '__main__'), ('__weakref__', <attribute '__weakref__' of 'Person' objects>), ('age', 3), ('showage', <function Person.showage at 0x0000000005A6DD90>)]
上例中,可以看到类属性保存在类的__dict__中,实例属性保存在实例的__dict__中,如果从实例访问类的属性,就需要借助__class__找到所属的类。再看下面的例子。
class Person: age = 3 height = 170 def __init__(self,name,age = 18): self.name = name self.age = age tom = Person("Tom")#实例化,初始化 jerry = Person("Jerry",20) Person.age = 30 print(Person.age,tom.age,jerry.age)#输出什么结果30,18,20 print(Person.height,tom.height,jerry.height)#输出结果是什么?170,170,170 jerry.height = 175 print(Person.height,tom.height,jerry.height)#输出结果是什么?,为对象新增了一个属性 tom.height+=10 print(Person.height,tom.height,jerry.height)#输出结果是什么? Person.height+=15 print(Person.height,tom.height,jerry.height)#输出结果是什么? Person.weight= 70 print(Person.weight,tom.weight,jerry.weight)#输出结果是什么? print(tom.__dict__["height"]) print(tom.__dict__["weight"])#可以吗? 结果为: 30 18 20 170 170 170 170 170 175 170 180 175 185 180 175 70 70 70 180 --------------------------------------------------------------------------- KeyError Traceback (most recent call last) <ipython-input-20-5ca4753173bd> in <module> 26 27 print(tom.__dict__["height"]) ---> 28 print(tom.__dict__["weight"])#可以吗? KeyError: 'weight'
总结:是类的,也是这个类所有实例的,类的实例都可以访问到,是实例的,就是这个实例自己的,通过类是访问不到的。
类变量是属于类的变量,这个类的所有实例可以共享这个变量。
实例可以动态的给自己增加一个属性,实例.__dict__[变量名]和实例.变量名都可以访问到,实例的同名变量会隐藏这类变量,或者说是覆盖了这个类变量。
实例属性的查找顺序
指的是实例使用.来访问属性,它会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找。
注意:如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量,这是指明使用字典的key查找,不是属性查找。
一般来说,类变量使用全大学来命名。
装饰一个类
首先先回顾以下是什么是高阶函数,什么是装饰器函数?然后思考,如何装饰一个类。需求:为一个类通过装饰,增加一些类属性。例子有问题
#增加类变量 def add_name(name,cls): cls.name = name#动态增加属性 #改进成装饰器 def add_name(name): def wrapper(cls): cls.name = name return cls return wrapper @add_name("tom") class Person: AGE = 3 print(Person.name)
结果为:
tom
之所以能够装饰,本质上是为类对象动态的添加了一个属性,而Person这个标识符指向这个类对象。
def setnameproperty(name): def wrapper(cls): cls.NAME = name return cls return wrapper @setnameproperty("MY CLASS") class MyClass: pass print(MyClass.__dict__) 结果为: {'__module__': '__main__', '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None, 'NAME': 'MY CLASS'}
class MyClass: x= 123 def __init__(self):#初始化 print("init") def foo(self): print("foo") def bar(): print("bar") a = MyClass()#实例化,初始化 a.foo() MyClass.bar() print(MyClass.__dict__) a.bar() 结果为: init foo bar {'__module__': '__main__', 'x': 123, '__init__': <function MyClass.__init__ at 0x00000000057B4F28>, 'foo': <function MyClass.foo at 0x00000000057B4E18>, 'bar': <function MyClass.bar at 0x00000000057B4D08>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None} --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-7-cc6d22bdbb59> in <module> 14 print(MyClass.__dict__) 15 ---> 16 a.bar() TypeError: bar() takes 0 positional arguments but 1 was given
类方法和静态方法
前面的例子中定义的__init__等方法,这些方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类必须实例化之后,由实例来调用这个方法。
普通函数
class Person: def normal_method():#可以吗? print("normal") #如何调用 Person.normal_method()#可以吗? print(Person.__dict__) Person().normal_method()#可以吗? 不可以调用 结果为: normal {'__module__': '__main__', 'normal_method': <function Person.normal_method at 0x0000000005A6DA60>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None} --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-25-166c70d8d0b6> in <module> 6 Person.normal_method()#可以吗? 7 print(Person.__dict__) ----> 8 Person().normal_method()#可以吗? TypeError: normal_method() takes 0 positional arguments but 1 was given
Person.normal_method()是可以调用的,因为这个方法只是被Person这个名词空间管理的一个普通的方法,normal_method这是Person的一个属性而已。
由于normal_method在定义的时候没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用。
注意:虽然语法是对的,但是,没有人这么用,也就是说禁止这么写。
class MyClass: x= 123 def __init__(self):#初始化 print("init") def foo(self): return "foo = {}".format(self.x) def bar(): print("bar") a = MyClass()#实例化,初始化 print(a.foo()) MyClass.bar() print(MyClass.__dict__) 结果为: init foo = 123 bar {'__module__': '__main__', 'x': 123, '__init__': <function MyClass.__init__ at 0x00000000057B4598>, 'foo': <function MyClass.foo at 0x00000000057B47B8>, 'bar': <function MyClass.bar at 0x00000000057B4840>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None}
类方法
class Person: @classmethod def class_method(cls):#cls是什么 print("class = {0.__name__} ({0})".format(cls)) cls.HEIGHT = 170 Person.class_method() print(Person.__dict__) 结果为: class = Person (<class '__main__.Person'>) {'__module__': '__main__', 'class_method': <classmethod object at 0x0000000005A729E8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}
class MyClass: x= 123 def __init__(self):#初始化 print("init") def foo(self): print("foo") def bar(): print("bar") @classmethod def clsmtd(cls): print("{}.x".format(cls.__name__,cls.x)) a = MyClass()#实例化,初始化 a.foo() MyClass.bar() print(MyClass.__dict__) MyClass.clsmtd() a.clsmtd()#等价于a.__class__.clsmtd() 结果为: init foo bar {'__module__': '__main__', 'x': 123, '__init__': <function MyClass.__init__ at 0x00000000057B4158>, 'foo': <function MyClass.foo at 0x00000000057B4A60>, 'bar': <function MyClass.bar at 0x00000000057B47B8>, 'clsmtd': <classmethod object at 0x00000000057C1860>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None} MyClass.x MyClass.x
类方法:
- 在类定义中,使用@classmethod装饰器装饰的方法。
- 必须至少有一个参数,且第一个参数留给了cls,cls指代调用者即类对象自身。
- cls这个标识符可以是任意合法名称,但是为了易读,请不要修改。
- 通过cls可以直接操作类的属性。
注意:无法通过cls操作类的实例。为什么?因为根本看不见类的实例。
类方法,类似于c++,Java中的静态方法。
静态方法
class Person: @classmethod def class_method(cls):#cls是什么 print("class = {0.__name__} ({0})".format(cls)) cls.HEIGHT = 170 @staticmethod def static_method(): print(Person.HEIGHT) Person.class_method() Person.static_method() print(Person.__dict__) class = Person (<class '__main__.Person'>) 170 {'__module__': '__main__', 'class_method': <classmethod object at 0x0000000005A72828>, 'static_method': <staticmethod object at 0x0000000005A72860>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'HEIGHT': 170}
静态方法
- 在类定义中,使用@staticmethod装饰器修饰的方法。
- 调用时,不会隐式的传入参数。
静态方法,只是表明这个方法属于这个名词空间,函数归在一起,方便组织管理。
方法的调用
类可以定义这么多中方法,究竟应该如何调用它们?
class Person: def normal_method(): print("normal") def method(self): print("{}'s method".format(self)) @classmethod def classmethod(cls):#cls是什么? print("class = {0.__name__} ({0})".format(cls)) cls.HEIGHT = 170 @staticmethod def static_method(): print(Person.HEIGHT) print("--------类访问") print(1,Person.normal_method())#可以吗? print(2,Person.method()) 结果为: --------类访问 normal 1 None --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-30-a4baf47d5334> in <module> 18 print("--------类访问") 19 print(1,Person.normal_method())#可以吗? ---> 20 print(2,Person.method()) 21 TypeError: method() missing 1 required positional argument: 'self' print(3,Person.class_method()) 结果为:
class = Person(<class"__main__.person">)
3 None print(4,Person.static_method()) 结果为: 170
4 none print(Person.__dict__) 结果为: {'__module__': '__main__', 'normal_method': <function Person.normal_method at 0x0000000005A5DC80>, 'method': <function Person.method at 0x0000000005A5D950>, 'classmethod': <classmethod object at 0x0000000005A6EC50>, 'static_method': <staticmethod object at 0x0000000005A6ECF8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None} print("--------实例访问") print("tom----------") tom = Person() print(1,tom.normal_method()) 结果为: --------实例访问 tom---------- --------------------------------------------------------------------------- TypeError Traceback (most recent call last) <ipython-input-38-1d0d497b8a1d> in <module> 2 print("tom----------") 3 tom = Person() ----> 4 print(1,tom.normal_method()) TypeError: normal_method() takes 0 positional arguments but 1 was given print(2,tom.method()) 结果为: <__main__.Person object at 0x00000000059D1550>'s method 2 None print(3,tom.class_method()) 结果为:
class = Person (<class '__main__.Person'>) 3 None
print(4,tom.static_method()) 结果为:
170 4 None
print("jerry----------")
jerry = Person()
print(1,jerry.normal_method())
结果为: jerry---------- ---------------------------------------------------------------------------
TypeError Traceback (most recent call last) <ipython-input-46-023d6a6671c6> in <module> 1 print("jerry----------") 2 jerry = Person() ----> 3 print(1,jerry.normal_method()) TypeError: normal_method() takes 0 positional arguments but 1 was given
print(2,jerry.method())
结果为: <__main__.Person object at 0x0000000005A575F8>'s method 2 None
print(3,jerry.class_method()) 结果为:
class = Person (<class '__main__.Person'>) 3 None
print(4,jerry.static_method()) 结果为:
170 4 None
类几乎可以调用所有内部定义的方法,但是调用普通的方法时会报错,原因是第一参数必须是类的实例。
实例也几乎可以调用所有的方法,普通的函数的调用一般不可能出现,因为不允许这么定义。
总结:
类除了普通方法都可以调用,普通方法需要对象的实例作为第一参数。
实例乐意调用所有类中定义的方法(包括类方法,静态方法),普通方法传入实例自身,静态方法和类方法需要找到实例的类。
访问控制
私有属性
class Person: def __init__(self,name,age = 18): self.name = name self.age = age def growup(self,i=1): if i>0 and i<150:#逻辑控制 self.age+=i p1 = Person("tom") p1.growup(20)#正常的范围 print(p1.age) p1.age = 160#超过了范围,并绕过了控制逻辑 print(p1.age) 结果为: 38 160
上面的例子,本来是想通过方法控制属性,但是由于属性在外部可以访问,或者说可见,就可以直接绕过方法,直接修改这个属性。
Python提供了私有属性可以解决这个问题。
私有属性:使用双下划线开头的属性,就是私有属性。
class Person: def __init__(self,name,age = 18): self.name = name self.__age = age def growup(self,i=1): if i>0 and i<150:#逻辑控制 self.__age+=i p1 = Person("tom") p1.growup(20)#正常的范围 print(p1.__age)#不能再访问到了。 结果为: --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-61-80156cc852e4> in <module> 10 p1 = Person("tom") 11 p1.growup(20)#正常的范围 ---> 12 print(p1.__age) AttributeError: 'Person' object has no attribute '__age'
通过实验可以看出,外部已经访问不到__age了,age根本没有定义,更是访问不到。
class Person: def __init__(self,name,age = 18): self.name = name self.__age = age def growup(self,i=1): if i>0 and i<150:#逻辑控制 self.__age+=i def getage(self): return self.__age print(Person("tom").getage()) 结果为: 18
class Person: def __init__(self,name,age = 18): self.name = name self.__age = age def growup(self,i=1): if i>0 and i<150:#逻辑控制 self.__age+=i def getage(self): return self.__age tom = Person("tom") tom.growup(2) print(tom.getage()) 结果为: 20
私有变量的本质
外部访问不到,能够动态增加一个__age呢?
class Person: def __init__(self,name,age = 18): self.name = name self.__age = age def growup(self,i=1): if i>0 and i<150:#逻辑控制 self.__age+=i def getage(self): return self.__age tom = Person("tom") tom.growup(2) print(tom.getage()) tom._Person__age=200 print(tom.getage()) tom.age=300 print(tom.getage()) print(tom.age) print(tom.__dict__) 结果为: 20 200 200 300 {'name': 'tom', '_Person__age': 200, 'age': 300}
class Person: def __init__(self,name,age = 18): self.name = name self.__age = age def growup(self,i=1): if i>0 and i<150:#逻辑控制 self.__age+=i def getage(self): return self.__age p1 = Person("tom") p1.growup(20)#正常的范围 print(p1.__age)# 结果为: --------------------------------------------------------------------------- AttributeError Traceback (most recent call last) <ipython-input-64-569718545c30> in <module> 13 p1 = Person("tom") 14 p1.growup(20)#正常的范围 ---> 15 print(p1.__age)# AttributeError: 'Person' object has no attribute '__age' p1.__age = 28 print(p1.__age) print(p1.getage()) #为什么年龄不一样?__age没有覆盖吗? print(p1.__dict__) 结果为: 28 38 {'name': 'tom', '_Person__age': 38, '__age': 28}
秘密都在__dict__中,里面是{__age:28,_person__age:38,name;tom}
所以私有变量的本质:类定义的时候,如果声明一个实例变量的时候,使用双下划线,Python解释器会将其改名,转换名称为_类名__变量名的名称,所以用原来的名字访问不到了。
知道了这个名字,能否直接修改呢?
class Person: def __init__(self,name,age = 18): self.name = name self.__age = age def growup(self,i=1): if i>0 and i<150:#逻辑控制 self.__age+=i def getage(self): return self.__age p1 = Person("tom") p1.growup(20)#正常的范围 #print(p1.__age)#访问不到 p1.__age = 28 print(p1.__age) print(p1.getage()) #为什么年龄不一样?__age没有覆盖吗?没有被覆盖,这是两个key print(p1.__dict__) #直接修改私有变量 p1._Person__age = 15 print(p1.getage()) print(p1.__dict__) 结果为: 28 38 {'name': 'tom', '_Person__age': 38, '__age': 28} 15 {'name': 'tom', '_Person__age': 15, '__age': 28}
从上面的例子可以看到,知道私有变量的新名字,就可以直接从外部访问到,并可以修改它。
保护变量
在变量名前使用一个下划线,称为保护变量。
class Person: def __init__(self,name,age = 18): self.name = name self._age = age tom = Person("tom") print(tom._age) print(tom.__dict__) 结果为: 18 {'name': 'tom', '_age': 18}
可以看出,这个_age属性根本就没有改变名称,和普通的属性一行,解释器不会做任何特殊处理,这只是开发者共同的约定,看见这种变量,就如同私有变量,不能直接使用。
私有方法
参照保护变量、私有变量,使用单下划线、双下划线命名方法。
class Person: def __init__(self,name,age = 18): self.name = name self._age = age def _getname(self): return self.name def __getage(self): return self._age tom = Person("tom") print(tom._getname())#没改名 #print(tom.__getage())#无此属性 print(tom.__dict__) print(tom.__class__.__dict__) print(tom._person__getage())#改名了
结果为:
tom {'name': 'tom', '_age': 18} {'__module__': '__main__', '__init__': <function Person.__init__ at 0x0000000005A79400>, '_getname': <function Person._getname at 0x0000000005A79378>, '_Person__getage': <function Person.__getage at 0x0000000005A79620>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None} 18
私有方法的本质:
单下划线的方法只是开发者之间的规定,解释器不做任何改变。
双下划线的方法,是私有方法,解释器会改名,改名策略和私有变量相同,_类名__方法名,方法变量都在类的__dict__中可以找到。
私有成员的总结
在Python中使用单下划线或者是双下划线来标识一个成员被保护或者被私有化隐藏起来。但是,不管使用什么样的访问控制,都不能真正的阻止用户修改类的成员,Python中没有绝对的安全的保护成员或者私有成员。
因此,前导的下划线只是一个警告或者提醒,请遵守这个约定。除非真有必要,不要修改或者使用保护成员或者私有成员,更不要修改它们。
补丁
可以通过修改或者替换类的成员,使用者调用的方式并没有什么改变,但是,类提供的功能可能已经改变了。
猴子补丁(monkey patch),在运行时,对属性、方法,函数等进行动态替换,其目的往往为了通过替换、修改来增强、扩展原有代码的能力。这是个黑魔法,要慎用。
例子没有印上来。
现在有一个模块test2代码如下:
class Person(): def __init__(self,chinese,englsh,history): self.chinese = chinese self.eng = english self.his = history def getscore(self): return (self.chinese,self.eng,self.his)
但是上面的功能不太好,想要修改,有另外的一个模块有如下代码:
def getscore(self): return dict(chinese = self.chinese,english = self.eng,history = self.his)
现在在运行的模块下导入上面的模块。
from test2 import Person from test3 import getscore def monkeypatch4person(): Person.getsocre = getscore
monkeypatch4person()
student1 =Person(80.90,88)
print(student1.getscore())
这样就可以动态的替换了,想要在什么地方替换都可以。可以在适当的地方打补丁。上面的因为是在jupyter里面,模块有点问题。不能运行。
属性装饰器
一般比较好的设计是:把实例的属性保护起来,不让外部直接访问,外部使用getter读取属性和setter方法设置属性。
class Person: def __init__(self,name,age = 18): self.name = name self.__age = age def age(self): return self.__age def set_age(self,age): self.__age = age tom = Person("tom") print(tom.age()) tom.set_age(20) print(tom.age()) 结果为: 18 20
通过age和set_age方法操作属性,有没有简单的方式?
class Person: def __init__(self,name,age = 18): self.name = name self.__age = age @property def age(self): return self.__age @age.setter def age(self,age): self.__age = age @age.deleter def age(self): print("del") tom = Person("tom") print(tom.age()) tom.age = 20 print(tom.age) del tom.age
特别注意:使用property装饰器的时候这个方法同名。
property装饰器
后面跟的函数名就是以后的属性名,他就是getter,这个必须有,有了它至少是只读属性。它将方法伪装成属性来访问。
setter装饰器
与属性名同名,且接收2个参数,第一个是self,第二是将要赋值的值,有了它,属性可写。
deleter装饰器
可以控制是否删除属性,很少用。
property装饰器必须在前,setter,deleter装饰器在后,property装饰器能通过简单的方式,把对方法的操作变成属性的访问你,变起到了一定的隐藏效果。其他的写法如下:
class Person(): def __init__(self,name,age=18): self.name = name self.__age = age def getage(self): return self.__age def setage(self,age): self.__age = age def delage(self): #def self.__age print("del") age =property(getage,setage,delage,"age property") tom = Person("tom") print(tom.age) tom.age = 20 print(tom.age) del tom.age 结果为: 18 20 del
还可以如下:
class person(): def __init__(self,name,age=18): self.name = name self.__age = age age = property(lambda self:self.__age) tom = Person("tom") print(tom.age) 结果为: 18
对象的销毁
类中可以定义__del__方法,称为析构函数(方法)
作用:销毁类的实例的时候调用,以释放占用的资源。其中就放些清理资源的代码,比如释放连接。
注意这个方法不能引起对象的真正销毁,只是对象销毁的时候会自动调用它。
使用del语句删除实例,引用计算减1,当引用计算为0时,会自动调用__del__方法。
由于Python实现了垃圾回收机制,不能确定对象核实执行垃圾回收。
import time class Person: def __init__(self,name,age = 18): self.name = name self.__age = age def __del__(self): print("delete {}".format(self.name)) def test(): tom = Person("tom") tom.__del__() tom.__del__() tom.__del__() tom.__del__() print("=======start=========") tom2 = tom tom3=tom2 print(1,"del") del tom time.sleep(3) print(2,"del") del tom2 time.sleep(3) print("~~~~~~~~~~~~~~~~~~~~") del tom3#注释一下,看看效果 time.sleep(3) print("==========end") test() 结果为: delete tom delete tom delete tom delete tom =======start========= 1 del 2 del ~~~~~~~~~~~~~~~~~~~~ delete tom ==========end
由于垃圾回收对象销毁时,才会真正清理对象,还会再之前调用__del__方法,除非你明确知道自己的目的,建议不要手动调用这个方法。
方法重载
在其他面向对象的高级语言中, 都有重载的这个概念,所谓重载,就是同一个方法名,但是参数数量,类型不一样,就是同一个方法的重载。
Python没有重载,Python也不需要重载。
Python中,方法(函数)定义中,形参非常的靓货,不需要指定类型(就算指定了也只是一个说明而非约束),参数个数也不固定(可变参数)。一个函数的定义可以实现很多种不同形式实参的调用。所以Python不需要方法的重载。
或者说Python本身就实现了其他语言的重载。
封装
面向对象的三要素之一,封装encapsulation.
封装
将数据和操作组织到类中,即属性和方法。
将数据隐藏起来,给使用者提供操作(方法),使用者通过操作可以获取或者修改数据,getter,setter。
通过访问控制,暴露适当的数据和操作给用户,该隐藏的隐藏起来,例如保护成员和私有成员。
练习1
随机整数生成类,可以指定一批生成的个数,可以指定数值的范围,可以调整每批生成数字的个数。
import random #1普通类实现 class RandomGen(): def __init__(self,start = 1,stop=100,count=100): self.start = start self.stop = stop self.count = count def generate(self,start = 1,stop = 100,count = 10): return [random.randint(self.start,self.stop) for x in range(self.count)] a = RandomGen(10,15,6) a.generate() 结果为: [10, 12, 11, 12, 10, 12]
#2 作为工具类来实现,提供类方法 class RandomGen(): @classmethod def generate(cls,start = 1,stop = 100,count= 10): return [random.randint(start,stop) for x in range(count)] RandomGen.generate(10,20,6) 结果为: [16, 10, 19, 14, 16, 19]
随机整数生成类,可以指定一批生成的个数,可以指定数值范围,可以调整每批生成数字的个数。
使用生成器实现,如下:
import random class RandomGenerator(): def __init__(self,start = 1,stop=100,patch=10): self.start = start self.stop=stop self.patch = patch self._gen = self._generate() def _generate(self): while True: yield random.randint(self.start,self.stop) def generate(self,count = 0): if count<=0: return [next(self._gen) for _ in range(self.patch)] else: return [next(self._gen) for _ in range(count)] a = RandomGenerator() print(a.generate()) print(a.generate(5)) 结果为: [87, 31, 83, 41, 46, 63, 2, 93, 74, 31] [72, 46, 27, 92, 77]
#生成器另一种实现 import random class RandomGenerator(): def __init__(self,start = 1,stop=100,patch=10): self.start = start self.stop=stop self.patch = patch self._gen = self._generate() def _generate(self): while True: yield [random.randint(self.start,self.stop) for _ in range(self.patch)] def generate(self,count = 0): if count>0: self.patch = count return next(self._gen) a = RandomGenerator() print(a.generate()) print(a.generate(5)) 结果为: [47, 40, 29, 70, 3, 27, 24, 68, 79, 21] [49, 20, 21, 11, 93]
#使用property class RandomGenerator(): def __init__(self,start = 1,stop=100,patch=10): self.start = start self.stop=stop self.patch = patch self._gen = self._generate() def _generate(self): while True: yield [random.randint(self.start,self.stop) for _ in range(self.patch)] def generate(self): return next(self._gen) @property def patch(self): return self._patch @patch.setter def patch(self,vaule): self._patch = vaule a = RandomGenerator() print(a.generate()) a.patch = 5 print(a.generate()) 结果为: [66, 46, 23, 21, 85, 91, 4, 22, 43, 100] [37, 66, 88, 51, 65]
练习2
打印坐标,使用上题中的类,随机生成20个数字,两两配对形成二维坐标系的坐标,把这些坐标组织起来,并打印输出。
class Point(): def __init__(self,x,y): self.x = x self.y = y points = [Point(x,y) for x,y in zip(RandomGenerator(10).generate(),RandomGenerator(10).generate())] for p in points: print("{}:{}".format(p.x,p.y)) 结果为: 21:18 38:29 10:86 95:32 60:74 83:37 35:54 100:73 33:27 45:15
练习3
车辆信息。记录车的品牌Mark、颜色color、价格price、速度speed等特征,并实现增加车辆信息,显示全部车辆信息的功能。
class Car():#记录单一车辆 def __init__(self,mark,speed,color,price): self.mark = mark self.speed = speed self.color = color self.price = price class CarInfo(): def __init__(self): self.info = [] def addcar(self,car:Car): self.info.append(car) def getall(self): return self.info ci = CarInfo() car = Car("audi",400,"red",100) ci.addcar(car) ci.getall()#返回所有数据,此时在实现格式打印 结果为: [<__main__.Car at 0x3d905b0>]
练习4
实现温度的处理。
思路:
假定一般情况下,使用摄氏度为单位,传入温度值。
如果不给定摄氏度,一定会把温度值转换到摄氏度。
温度转换方法可以使用实例的方法,也可以使用类方法。使用类方法的原因是,为了不创建对象,就可以直接进行温度转换计算,这个类设计像个温度工具类。
class Temperature(): def __init__(self,t,unit = "c"): self._c = None self._f = None self._k = None if unit=="k": pass elif unit=="f": pass else: self._c = t @property def c(self):#摄氏度 return self._c @property def k(self):#开氏温度 pass @property def f(self):#华氏温度 pass #温度转换 @classmethod def c2f(cls,c): return 9*c/5 +32 @classmethod def f2c(cls,f): return 5*(f-32)/9 @classmethod def c2k(cls,c): return c+273.15 @classmethod def k2c(cls,k): return k-273.15 @classmethod def f2k(cls,f): return cls.c2k(cls.f2c(f)) @classmethod def k2f(cls,k): return cls.c2f(cls.k2c(k))
进一步完善代码,如下:
class Temperature(): def __init__(self,t,unit = "c"): self._c = None self._f = None self._k = None #都要先转换到摄氏度,以后访问再计算其他单位的温度值 if unit=="k": self._k = t self._c = self.k2c(t) elif unit=="f": self._f = t self._c = self.f2c(t) else: self._c = t @property def c(self):#摄氏度 return self._c @property def k(self):#开氏温度 if self._k is None: self._k = self.c2k(self._c) return self._k @property def f(self):#华氏温度 if self._f is None: self._f = self.c2f(self._c) return self._f #温度转换 @classmethod def c2f(cls,c): return 9*c/5 +32 @classmethod def f2c(cls,f): return 5*(f-32)/9 @classmethod def c2k(cls,c): return c+273.15 @classmethod def k2c(cls,k): return k-273.15 @classmethod def f2k(cls,f): return cls.c2k(cls.f2c(f)) @classmethod def k2f(cls,k): return cls.c2f(cls.k2c(k)) print(Temperature.c2f(40)) print(Temperature.c2k(40)) print(Temperature.f2c(104.0)) print(Temperature.k2c(313.15)) print(Temperature.k2f(313.15)) print(Temperature.f2k(104)) t = Temperature(37) print(t.c,t.k,t.f) t = Temperature(300,"k") print(t.c,t.k,t.f) 结果为: 104.0 313.15 40.0 40.0 104.0 313.15 37 310.15 98.6 26.850000000000023 300 80.33000000000004
练习5
模拟购物车购物
思路
购物车是购物,分解得到两个对象,购物车,物品。一个操作:买
购买不是购物车的行为,其实是人的行为,但是对于购物车来说就是增加add.
商品有很多种类,商品的属性多种多样,怎么解决?
购物车可以加入很多不同的商品,如何实现?
class Color(): RED = 0 BLUE =1 GREEN = 2 GOLDEN = 3 BLACK = 4 OTHER = 1000 class Item(): def __init__(self,**kwargs): self.__spec = kwargs def __repr__(self): return str(sorted(self.__spec.items())) class Cart(): def __init__(self): self.items = [] def additem(self,item:Item): self.items.append(item) def getallitems(self): return self.items mycart = Cart() myphone = Item(mark= "huawei",color = Color.GOLDEN,memory = "4g") mycart.additem(myphone) mycar = Item(mark= "red flag",color = Color.BLACK,year = 2017) mycart.additem(mycar) print(mycart.getallitems()) 结果为: [[('color', 3), ('mark', 'huawei'), ('memory', '4g')], [('color', 4), ('mark', 'red flag'), ('year', 2017)]]
注意,上面的代码只是一个非常简单的实现,生成环境实现购物车的增删改查,要考虑很多。