类与对象
# 类的声明,括号中表示继承自哪个类,没有的话一般都是继承自object
class Student(object):
# 类属性
age = 18
# 类似于Java中的构造方法, 给属性一些初始化值
def __init__(self, name, score, sex):
# 实例属性
self.name = name
self.score = score
# 被双下划线"__"修饰的都是private属性,不能被外部访问
self.__sex = sex
"""
类中的方法,与普通函数的区别:第一个参数永远是self,表示创建的实例本身;
外部调用方法时不需要传self
"""
def print_info(self):
print("%s,%d,%s" % (self.name, self.score, self.__sex))
"""
提供set、get方法, 用于外部使用者对属性进行修改和获取
"""
def set_sex(self, sex):
self.__sex = sex
def get_sex(self):
return self.__sex
# 创建对象,并传入类属性初始化值
s = Student("Blain", 90, "male")
s.print_info() # Blain,90,male
s.score = 95
s.print_info() # Blain,95,male
s.set_sex("female")
print(s.get_sex()) # female
# 获取对象信息
print(hasattr(s, "name")) # True
# 判断是否有属性
print(hasattr(s, "grade")) # False
# 设置属性
setattr(s, "grade", 9)
print(hasattr(s, "grade")) # True
# 没有这个属性会报错
# print(getattr(s, "address"))
# 无属性时也可以自定义返回结果
print(getattr(s, "address", "没有这个属性")) # 没有这个属性
# 获取方法信息
fn = getattr(s, "print_info")
print(fn) # <bound method Student.print_info of <__main__.Student object at 0x100686320>>
fn() # Blain,95,female
# 类属性即可以用类来获取,也可以通过实例来获取,但实例属性的优先级比类属性要高
print(Student.age) # 18
print(s.age) # 18
面向对象高级编程
使用slots
-
给一个类动态的绑定属性和方法
from types import MethodType class People(object): pass p1 = People() p2 = People() # 动态的给实例p1绑定name属性 p1.name = "Blain" # 定义一个函数作为实例方法 def set_age(self, age): self.age = age # 动态的给实例p1绑定一个方法 p1.set_age = MethodType(set_age, p1) print(p1.name) # Blain p1.set_age(18) print(p1.age) # 18 # 给一个实例绑定的方法,对另一个实例是不起作用的 # p2.set_age(19) # AttributeError: 'People' object has no attribute 'set_age' # 为了给所有实例都绑定方法,可以给class绑定方法: def set_score(self, score): self.score = score People.set_score = set_score p1.set_score(80) print(p1.score) # 80 p2.set_score(90) print(p2.score) # 90
-
限制实例的属性
class People2(object): # 用tuple定义允许绑定的属性名称 __slots__ = ("name", "age") p3 = People2() p3.name = "Tom" p3.age = 16 # p3.score = 99 # AttributeError: 'People2' object has no attribute 'score'
slots定义的属性仅对当前类实例起作用,对继承的子类是不起作用
使用@property
修改和获取属性,可以通过实例对象直接获取属性来进行修改,但若这个属性是private的话就没办法修改;然后也可以通过类对外提供的set和get方法来获取属性或修改,不过这样的话略显繁琐。有没有既能检查参数,又可以用类似属性这样简单的方式来访问类的变量呢?之前有学到装饰器(decorator)可以给函数动态加上功能,Python内置的@property装饰器就是负责把一个方法变成属性调用的
class Student(object):
@property
def score(self):
return self.__score
@score.setter
def score(self, value):
if not isinstance(value, int):
raise ValueError("score must be integer")
if value < 0 or value > 100:
raise ValueError("score must be between 0 ~ 100")
self.__score = value
s = Student()
s.score = 30 # 这里实际转化为s.set_score(60)
print(s.score) # 30 这里实际转化为s.get_score()
s.score = 1000
print(s.score) # ValueError: score must be between 0 ~ 100
多重继承
一个类是可以继承另一个类的,但以前都是单继承,若需要“混入”额外的功能,通过多重继承就可以实现。在Python中,这种设计通常称之为MixIn
class Animal(object):
pass
class Mamal(Animal):
pass
class Bird(Animal):
pass
class RunnableMixIn(object):
def run(self):
print("可以跑的动物")
class FlyableMixIn(object):
def fly(self):
print("可以飞的动物")
class Dog(Mamal, RunnableMixIn):
pass
# 很显然,鹦鹉既属于鸟类,也具有飞的功能
class Parrot(Bird, FlyableMixIn):
pass
但在Java中,一个类只能够进行单继承,所以MixIn这种设计不适用Java。
定制类
class Student(object):
def __init__(self, name):
self.name = name
self.a, self.b = 0, 1 # 初始化两个计数器a,b
# 自定义类输入信息,有点类似于java中的toString方法, 返回用户看到的字符串
def __str__(self):
return "Student object (name: %s)" % self.name
# 当在命令行交互模式下直接调用实例变量时,会调用这个方法,返回程序开发者看到的字符串;代码一般与__str__一致
__repr__ = __str__
# 如果一个类想被用于for ... in循环,就必须实现__iter__方法
def __iter__(self):
return self # 实例本身就是迭代对象,故返回自己
# 然后for循环就会不断调用该迭代对象的__next__方法拿到循环的下一个值,直到遇到StopIteration错误时退出循环
def __next__(self):
self.a, self.b = self.b, self.a + self.b # 计算下一个值
if self.a > 20:
raise StopIteration
return self.a
# 实现此方法能够使Student像list一样通过索引获取值
def __getitem__(self, item):
a, b = 1, 1
if isinstance(item, int): # item代表的是索引
for x in range(item):
a, b = b, a + b
return a
if isinstance(item, slice): # item代表的是切片
start = item.start
stop = item.stop
if start is None:
start = 0
l = []
for x in range(stop):
if x >= start:
l.append(a)
a, b = b, a + b
return l
# 当调用不存在的属性时,Python解释器会试图调用此方法来获得属性
# 这实际上可以把一个类的所有属性和方法调用全部动态化处理了,不需要任何特殊手段。
def __getattr__(self, attr):
if attr == "sex":
return "Male"
raise AttributeError('\'Student\' object has no attribute \'%s\'' % attr)
# 实现此方法能够让对象像函数那样直接调用,也就是把对象看成函数,把函数看成对象,它们都是在运行期间创建出来的
# 这样一来,我们就模糊了对象和函数数的界面,那如何来判断一个变量是对象还是函数呢? 可能通callable函数
def __call__(self):
print("My name is %s" % self.name)
# print(Student("Blain")) # Student object (name: Blain)
# for n in Student("Blain"):
# print(n) # 1,1,2,3,5,8,13
s = Student("Blain")
# print(s[0]) # 1
# print(s[1]) # 1
# print(s[2]) # 2
# print(s[3]) # 3
# print(s[4]) # 5
# print(s[:5]) # [1,1,2,3,5]
# print(s.age) # AttributeError: 'Student' object has no attribute 'age'
print(callable(s)) # True
print(callable("test")) # False
使用枚举类
Enum可以把一组相关常量定义在一个class中,且class不变,而且成员也可以直接比较
from enum import Enum, unique
Month = Enum("Month", ("Jan", "Feb", "Mar", "Apr", "May", "June", "July"))
# 单独的引用一个常量
print(Month.Jan) # Month.Jan
# 枚举它的所有成员
for name, member in Month.__members__.items():
# value属性是自动赋给成员的int常量,默认是从1开始
print(name, "=>", member, ",", member.value)
# Jan => Month.Jan , 1
# Feb => Month.Feb , 2
# Mar => Month.Mar , 3
# Apr => Month.Apr , 4
# May => Month.May , 5
# June => Month.June , 6
# July => Month.July , 7
# 自定义枚举类
@unique # @unique装饰器可以帮助我们检查保证没有重复值
class Weekday(Enum):
Sun = 0 # Sun的Value被设置为0
Mon = 1
Tue = 2
Wed = 3
Thu = 4
Fri = 5
Sat = 6
d1 = Weekday.Mon
print(d1) # Weekday.Mon
print(Weekday.Mon) # Weekday.Mon
print(Weekday["Mon"]) # Weekday.Mon
print(Weekday(1)) # Weekday.Mon
print(Weekday.Mon.value) # 1
print(d1 == Weekday(1)) # True
print(d1 == Weekday.Mon) # True