python : 3.6
面向对象
类和对象
类 : 是具有某些相同特征行为个体的抽象(模板)。比如,人就是一个抽象概念
对象:是类的具体实现(实例)。比如,李四就是人的具体实现
属性和行为
属性:名词。定义 该类或实例所包含的数据。 比如名字,年龄等数据
行为:动词。定义实例所具有的行为特征。 比如 吃饭,睡觉,打豆豆等行为
构造函数
对象的创建需要使用构造函数。跟其他面向对象语言一样,分为无参构造函数和有参构造函数。默认情况下就提供了无参构造函数,如果需要在对象创建的时候就初始化对象属性,那么应该使用有参构造函数。
class Student:
def __init__(self, name):
self.name = name
stu1 = Student('张三')
stu2 = STudent('李四')
print(stu1.name)
print(stu2.name)
----------------------
'张三'
'李四'
和Java语言类似的,构造函数 __init__
没有具体返回值(返回None)。根据java来理解的话,其实是有返回值的,它的返回值就是就是该类的实例,不过是隐式返回的,而且不能再构造函数中显示返回,否则抛出异常
类变量和实例变量
class Student():
name = 'static_name' #类变量,写在方法之外
def __init__(name):
self.name = name #实例变量,写在__init__方法中
stu = Student('小明')
print(stu.name)
print(Student.name)
-------------------------------------------------
#结果
'小明'
'static_name'
调用对象属性时步骤:
- 先查找是否定义了同名实例变量,如果存在则直接返回
- 查找是否定义了同名类变量,存在直接返回
- 如果找不到则抛出异常
AttributeError: 'xx' object has no attribute 'xx'
实例方法调用某个变量时的步骤:
- 在方法体内是否存在同名的参数,有则直接返回
- 抛出异常
NameError: name 'xxx' is not defined
总结:
- 静态变量和实例变量的调用需要有前缀(类名或self)
- 一般来说类变量应该使用类名调用,因为类变量不属于某个具体的对象
- 实例方法内调用类变量,
ClasName.xxx
,self.__class__.xxx
,self.xxx
(该方式如果存在同名的实例变量,则会调用的是实例
方法
与Java不同的是,python方法没有重载。可能是因为python是动态语言的缘故。
1.实例方法
def funName(self): # self只是一种约定俗称,也不是关键字。也可以写this等
pass
实例方法定义和一般方法相似,但是该方法必须有一个参数self
, 用于接收调用当前方法的类实例。
比较神奇的是,在python中可以直接使用类名调用实例方法。但是此时必须传入self参数。
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_info(self):
print("name:"+self.__name+"\tage:"+str(self.age))
person = Pserson("python小白", 20)
#1. person.get_info()
Person.get_info(person) #2.0
----------------------------
name:python小白 age:20
上面代码1 和 代码2 的执行结果一样
2.类方法
类方法和实例方法相似,不同的是方法需要使用@classmethod 修饰。@classmethod是装饰器,有点类似java注解。
类方法可以更简单的访问类变量。
@classmethod
def classMethod(cls): #cls用于接收 类
cls.xxx
python的静态方法,只是用于更简单的访问类变量和类方法。正常情况下,类方法只能访问类变量 和方法(不正常的情况,给类方法添加一个self参数,并传入实例,,这样做感觉没什么意义)
class Person:
sum = 0
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_info(self):
print("name:"+self.__name+"\tage:"+str(self.age))
@classmethod
def get_num(cls):
cls.sum += 1
print(cls.sum)
person = Person("xx",20)
person.get_num() # 1. 对象调用类方法
Person.get_num() #2. 直接使用类名调用类方法
--------------------------
1
2
从上面可以看出,虽说是类方法但是对象可以正常调用。不是说接收的参数cls
代表类吗???。
通过debug,就知道答案了,类方法即使被对象调用参数cls
被传入的还是类本身。
3.静态方法
@staticmethod
def staticMethod():
pass
静态方法没有什么特别的限制。同样对象和类可以直接调用。同样正常情况下不能访问类变量和实例变量 ,类方法和实例方法
方法总结:
- 静态方法没有什么好说的,什么都不能调用。除非传入类或者类实例
- 类方法,用于方便调用类变量和其他类方法。类实例也可以直接调用,python如果发现调用者是类实例,并且是类方法,则传入
self.__class__
。(不要问为什么,我猜的) - 实例方法,使用类名也可以调用,但是必须传入
self
,因为类是抽象的(模板)可以创建很多对象,所以必须知道是调用者是哪个实例。
私有属性或方法
- __private_attrs:两个下划线开头,声明该属性为私有,不能在类地外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs
- __private_method:两个下划线开头,声明该方法为私有方法,只能在类的内部调用 ,不能在类地外部调用。self.__private_methods。
其实这只是一种声明,python没有提供特别的机制来保证属性和方法的私有。
class Student():
def __init__(self,name):
self.name = name
def __get_name(self):
return self.name
stu = Student('xxx')
print(dir(stu))
print(stu._Student__get_name())
-------------------------------------------------------------
['_Student__get_name', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'name']
'xxx'
从上面可以看到多了一个_Student__get_name
。属性或方法私有化以后确实无法直接访问,python对此做了一些处理。但是通过增加前缀_ClassName
就可以访问了。
注:
前后都有双下划线在python中有特殊含义的函数或变量,不建议自定义的函数或方法这样写
继承
class DerivedClassName(ParentClassName):
<statement>
python还支持多继承
class DerivedClassName(ParentClassName1, ParentClassName2...):
<statement>
定义一个父类
class Person:
def __init__(self, name, age):
self.__name = name
self.__age = age
def get_name(self):
return seld.__name
调用父类构造函数:
隐式调用:可以不写构造函数,此时创建子类实例时,会隐式调用父类的构造函数。此时需要传入和父类构造函数相同数目的参数。否则会抛出异常TypeError: __init__() missing xx个 required positional arguments
class Student(Person): pass stu = Student("xxx", 10) # stu = Student() 会抛出 TypeError: __init__() missing 2 required positional arguments: 'name' and 'age'
显式调用
如果子类构造函数要显式调用父类的构造函数,可以通过父类名称调用构造函数
class Student(Person):
def __init__(self, name, age, addr) Person.__init__(self, name , age) self.__addr = addr
stu = Student(‘马什么梅’, 20, ‘电影’)
print(stu.get__name())通过super
class Student(Person): def __init__(self, name, age): super(Student,self).__init__(name, age)
当然也可以显式不调用父类的构造函数,但是这样做一般就失去了继承的用意。推荐使用第二种,因为第一种代码耦合性高
调用父类私有属性,或方法
调用子类与父类重名的属性或方法。
子类会覆盖父类中同名的方法和变量。如果要调用则需要显式调用。也是使用super
关键字
类的专有方法
类的专有方法:
__init__ : #构造函数,在生成对象时调用
__del__ : #析构函数,释放对象时使用
__repr__ : #打印,转换
__setitem__ : #按照索引赋值
__getitem__: #按照索引获取值
__len__: #获得长度
__cmp__: #比较运算
__call__: #函数调用
__add__:#加运算
__sub__: #减运算
__mul__: #乘运算
__div__: #除运算
__mod__: #求余运算
__pow__: #乘方
枚举
继承 Enum类
from enum inpotr Enum
class (Enum):
from enum import Enum
class Season(Enum):
SRPING = 1
SUMMER = 2
AUGUEST = 3
WINNER = 4
print(Season.SRPING) # 跟普通类不同的是,返回并不是变量值
print(Season.SPRING.value) # 返回具体值
print(Season.SPRING.name) # 返回标签名
print(type(Season.SRPING)) #实际上是一个定义枚举类的对象
print(Season.SPRING.__dict__)
print(Season(1)) # 根据值获取对应的枚举
-----------------------------
Season.SRPING
1
<enum 'Season'>
{'_value_': 1, '_name_': 'SPRING', '__objclass__': <enum 'Season'>}
Season.SRPING
枚举的好处:
- 可以防止出现相同的标签(对象?)。
TypeError: Attempted to reuse key: 'spring'
- 枚举标签不能被修改(常量),强制赋值会报错,
AttributeError: Cannot reassign members.
- 直接拿到的是标签名,而不是具体的值。这样可以直接根据标签名来识别变量的作用
注意:
- 枚举 之间不能做 >,< 等比较操做(可以等值比较)
- 如果一个枚举类,有多个枚举的值相同,则其他的枚举名称为最先出现枚举 的别名