面向对象三要素
1. 封装:
- 组装:将数据和操作组装到一起。
- 隐藏数据:对外只暴露一些借口,通过接口访问对象。比如驾驶员使用汽车,不需要了解汽车的构造细节,只需要知道使用什么部件怎么驾驶就行,踩了油门就能跑,可以不了解后面的机动原理。
2. 继承:
- 多复用,继承来的就不用自己写了
- 多继承少修改,OCP(Open-closed Principle),使用继承来改变,来体现个性。
3. 多态:
- 面向对象编程最灵活的地方,动态绑定
人类就是封装;
人类继承自动物类,孩子继承父母特征。分为单一继承、多继承;
多态、继承自动物类的人类、猫类的操作“吃”不同。
类
类的定义:
class ClassName:
语句块
- 必须使用class关键字
- 类名必须是用大驼峰
- 类定义完成后,就产生一个类对象,绑定到ClassName上
举例:
class MyClass:
""""A example class"""
x = 'abc' #类属性
def foo(sef): #类属性foo,也是方法
return 'My Class'
print(MaClass.x)
print(MaClass.foo)
print(MyClass.__doc__)
类属性与类对象
- 类对象 ,类在定义是就会生成一个类对象。
- 类的属性 ,类中定义的变量以及类中定义的方法都是类的属性。
- 类变量 ,类中定义的变量就是类变量,例如上例中的x就是类MyClass的变量。
MyClass中,x、foo都是类的属性,doc也是类属性。
foo方法是类的属性,如同吃是人类的方法,但每一个具体的人才能吃东西,也就是说吃是人的实例才能调用的方法。
foo是method方法对象 ,不是普通的函数对象function了,它必须至少有一个参数,且第一个参数必须是self(self可以换个名字),这个参数位置就留给了self。
self指代当前实例本身 。
实例化
mycls = MyClass() #实例化,初始化
mycls.foo()
print(mycls.x)
使用上面的语法,在类对象名称后面加一个括号,就调用类的实例化方法,完成实例化。实例化就真正的创建了一个该类的对象(实例)。例如人类的实例tom、jerry。
实例化后获得的实例,是不同的实例,即使是使用相同的参数实例化,也得到不一样的对象。Python类实例化后,会自动调用__int__方法。这个方法第一个参数必须留给self,其他参数随意。
__int__方法
MyClass()实际上调用的是__int__(self)方法,可以不定义,如果没有定义会在实例化后隐式调用。
作用:对实例化进行初始化 。
class MyClass:
x=123
def __init__(self): #实例化
print('init')
def foo(self):
return 'foo {}'.format(self.x)
a = MyClass()
print(a.foo())
运行结果:
init
foo 123
初始化参数可以有多个参数,请注意第一个位置必须是self,例如init(self,name,age)
class Person:
def __init__(self,name,age):
self.name = name
self.age = age
def show_age(self):
return self.age
tom = Person('Tom',23)
jerry = Person('Jerry',24)
print(tom .show_age())
tom.age += 1
print('Now {} is {}'.format(tom.name,tom.age))
print(jerry.show_age())
运行结果:
23
Now Tom is 24
24
注意:__init__()方法不能用返回值,也就是只能是None
实例对象instance
类实例化后一定会获得一个对象,就是实例对象。
上例中,a、b就是Person的类实例。
__init__方法的第一个参数self就是指代某一个实例。
类实例化出一个实例对象,实例对象会绑定方法,调用方法时采用jerrt.showage()的方法。
但是定义是showage(self),少传一个参数吗?
这个self就是jerry,Python会把方法的调用者作为第一个参数self的实参传入。
self.name就是jerry对象的那么,name是保存在了jerry对象上,而不是Person类上。所以,称为实例变量。
self
class MyClass:
def __init__(self):
print('self in init = {}'.format(id(self)))
c = MyClass()
print('c = {}'.format(id(c)))
运行结果:
self in init = 21997776
c = 21997776
上例说明,self就是调用者,就是c对应的实例对象。
self这个名字只是一个惯例,它可以修改,但请不要修改,否则影响代码的可读性
看打印的结果,思考一下执行的顺序,为什么?
–我的理解,在定义实例的时候,就进行了初始化,所以在c = MyClass()的时候,就先去__init__()函数里去执行代码了,所以self in init = 21997776要先执行到,并打印出来。
实例变量和类变量
class Person:
age = 3
def __init__(self,name):
self.name = name
tom = Person('Tom')
jerry = Person('Jerry')
print(tom.name,tom.age)
上例中,age = 3中age是类变量,self.name也是类变量。而tom.name,tom.age是实例变量
实例变量是每一个实例自己的变量,是自己独有的;类变量是类的变量,是类的所有实例共享的属性和方法
特殊属性 | 含义 |
---|---|
__name__ | 对象名 |
__class__ | 对象的类型 |
__dict__ | 对象的属性的字典 |
__qualname__ | 类的限定名 |
举例:
>>> class Person():
age = 3
def __init__(self,name):
self.name = name
>>>
>>> print(Person.__class__)
<class 'type'>
>>> print(Person.__dict__)
{'__module__': '__main__', 'age': 3, '__init__': <function Person.__init__ at 0x0142FAE0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
>>> tom = Person('Tom')
>>> print(tom.__class__)
<class '__main__.Person'>
>>>
>>> print(tom.__dict__)
{'name': 'Tom'}
>>> print(tom.__class__.__name__)
Person
>>> print(tom.__class__.__dict__)
{'__module__': '__main__', 'age': 3, '__init__': <function Person.__init__ at 0x0142FAE0>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
上例中,可以看到类属性保存在类__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)
>>> print(Person.age,tom.age,jerry.age)
3 18 20
>>> Person.age = 50
>>> print(Person.age,tom.age,jerry.age)
50 18 20
>>> print(Person.height,tom.height,jerry.height)
170 170 170
>>> Person.height = 190
>>> print(Person.height,tom.height,jerry.height)
190 190 190
>>> print(Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
{'__module__': '__main__', 'age': 50, 'height': 190, '__init__': <function Person.__init__ at 0x02614DF8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
{'name': 'Tom', 'age': 18}
{'name': 'Jerry', 'age': 20}
>>> tom.height = 160
>>> print(Person.height,tom.height,jerry.height)
190 160 190
>>> print(Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
{'__module__': '__main__', 'age': 50, 'height': 190, '__init__': <function Person.__init__ at 0x02614DF8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
{'name': 'Tom', 'age': 18, 'height': 160}
{'name': 'Jerry', 'age': 20}
>>> jerry.height += 20
>>> print(Person.height,tom.height,jerry.height)
190 160 210
>>> print(Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
{'__module__': '__main__', 'age': 50, 'height': 190, '__init__': <function Person.__init__ at 0x02614DF8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}
{'name': 'Tom', 'age': 18, 'height': 160}
{'name': 'Jerry', 'age': 20, 'height': 210}
>>>
>>>
>>> Person.weight = 140
>>> print(Person.__dict__,tom.__dict__,jerry.__dict__,sep='\n')
{'__module__': '__main__', 'age': 50, 'height': 190, '__init__': <function Person.__init__ at 0x02614DF8>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'weight': 140}
{'name': 'Tom', 'age': 18, 'height': 160}
{'name': 'Jerry', 'age': 20, 'height': 210}
>>> print(tom.weight)
140
>>> print(tom.__dict__['weight'])
Traceback (most recent call last):
File "<pyshell#92>", line 1, in <module>
print(tom.__dict__['weight'])
KeyError: 'weight'
总结:
是类的,也是这个类所有实例的,其实都可以访问到;是实例的,只是实例自己的,通过类访问不到。
类变量是属于类的变量,这个类的所有实例可以共享这个变量。
实例可以动态给自己增加一个属性。实例.__dict__[变量名]和实例.变量名都可以访问到。
实例的同名变量会隐藏这类变量,或者说是覆盖这个类变量。
实例属性的查找顺序:
指的是实例使用.来访问属性,会先找自己的__dict__,如果没有,然后通过属性__class__找到自己的类,再去类的__dict__中找。
注意,如果实例使用__dict__[变量名]访问变量,将不会按照上面的查找顺序找变量了。
一般来说,类变量使用全大写来命名。
装饰一个类
回顾,什么是高阶函数?什么是装饰器函数?
思考,如何装饰一个类?
需求,为一个类通过装饰,增加一些类属性
def addname(name):
def wrapper(cls):
cls.NAME = name
return cls
return wrapper
'''
#等价于 MyClass = addname('Tom')(MyClass)
#addname是一个带参数的函数,而返回的是wrapper这个函数对象,注意而不是函数的值
#所以addname(''Tom'),相当于是函数wrapper(),addname(''Tom')(MyClass)相当于是wrapper(MyClass),
# 然后此函数又把cls也就是MyClass返回给了MyClass实现了覆盖。
@addname('Tom')
class MyClass:
pass
print(MyClass.__dict__)
'''
class MyClass:
pass
MyClass = addname('Tom')(MyClass)
print(MyClass.__dict__)
运行结果:
{'__module__': '__main__', '__dict__': <attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None, 'NAME': 'Tom'}
类方法和静态方法
前面的例子中定义的__init__等方法,这些方法本身都是类的属性,第一个参数必须是self,而self必须指向一个对象,也就是类必须实例化之后,由实例来调用这个方法。
普通函数
class Person:
def normal_method(): #这样在类中定义函数可以吗?
print('naormal')
Person.normal_method() #这样调用函数可以吗?
Person().normal_method() #这样调用可以吗?
Person.normal_method()可以,因为这个方法只是被Person这个名字空间管理的一个普通方法,normal_method这是Person的一个属性而已。
由于normal_method在定义的时候,没有指定self,所以不能完成实例对象的绑定,不能用Person().normal_method()调用。
注意:虽然语法是对的,但是没有人这么用,也就是说,在类中,禁止写没有self的函数。
类方法
class Person:
@classmethod
def class_methon(cls): #cls是什么
print('class = {0.__name__} {0}'.format(cls))
cls.LEIGHT = 4
Person.class_methon()
print(Person.__dict__)
类方法
- 在类定义中,使用@classmethod装饰器修饰的方法
- 必须至少有一个参数,且第一个参数留给cls,cls知道调用者即类对象自身
- cls这个标识符可以是任意合法名称,但是为了易读,请不要修改
- 通过cls可以直接操作类的属性
注意:无法通过cls操作类的实例,为什么?
答:cls是类对象,简言之,类对象在调用这个类方式的时候,类实例,可以不用创建。所以,在没有类实例的情况下,cls还怎么去操作类实例呢?
类方法,类似于C++、Java的静态方法
静态方法
class Person:
@classmethod
def class_methon(cls): #cls是什么
print('class = {0.__name__} {0}'.format(cls))
cls.LEIGHT = 4
@staticmethod
def static_method():
print(Person.LEIGHT)
Person.class_methon()
Person.static_method()
print(Person.__dict__)
静态方法
- 在类的定义中,使用@staticmethod装饰器修饰的方法
- 调用时,不会隐式的传入参数
静态方法,只是表名这个方式属于这个名词空间。函数归在一起,方便组织管理。
方法的调用
class Person:
def normal(): #类普通函数,类可以直接调用。但禁止在类里这么定义函数
print('Normal method ')
def method(self): #普通方法,只能类的实例调用
print('method '.format(self.LEIGHT))
@classmethod #类方法,类可以直接调用
def class_methon(cls): #cls是什么
print('class = {0.__name__} {0}'.format(cls))
cls.LEIGHT = 4
@staticmethod #静态方法,类可以直接调用
def static_method():
print(Person.LEIGHT)
print('~~~~类访问~~~~')
print(1,Person.normal()) #可以吗?
#print(2,Person.method())#可以吗
print(3,Person.class_methon())#可以吗
print(4,Person.static_method())#可以吗
print(Person.__dict__)
print('~~~~实例访问~~~~')
print('Tom-------')
tom = Person()
#print(1,tom.normal())#可以吗
print(2,tom.method())#可以吗
print(3,tom.class_methon())#可以吗
print(4,tom.static_method())#可以吗
print(Person.__dict__)
输出结果:
~~~~类访问~~~~
Normal method
1 None
class = Person <class '__main__.Person'>
3 None
4
4 None
{'__module__': '__main__', 'normal': <function Person.normal at 0x01D8DF60>, 'method': <function Person.method at 0x01D8DF18>, 'class_methon': <classmethod object at 0x003EB470>, 'static_method': <staticmethod object at 0x01D95250>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'LEIGHT': 4}
~~~~实例访问~~~~
Tom-------
method
2 None
class = Person <class '__main__.Person'>
3 None
4
4 None
{'__module__': '__main__', 'normal': <function Person.normal at 0x01D8DF60>, 'method': <function Person.method at 0x01D8DF18>, 'class_methon': <classmethod object at 0x003EB470>, 'static_method': <staticmethod object at 0x01D95250>, '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None, 'LEIGHT': 4}
首先理解下输出结果:
print(3,Person.class_methon())#可以吗
输出的是
class = Person