9.1 创建和使用类

9.1.1 创建类

  1. class Dog():
  2. def __init__(self, name, age): #初始化属性name和age
  3. self.name = name
  4. self.age = age
  5. def sit(self):
  6. print(self.name.title() + " is now sitting.")
  7. def roll_over(self):
  8. print(self.name.title() + " rolled over!")
  9. my_dog = Dog('willie', 6)
  10. print("My dog's name is " + my_dog.name.title() + ".")
  11. print("My dog is " + str(my_dog.age) + " years old.")

注意:
(1):根据约定,类的名称要首字母大写
(2):类中的函数称为“方法”
(3):可通过实例访问的变量称为“属性”
(4):init() 是一种特殊的方法,名称的开头和末尾必须为“双下划线”!!!!!
init()支持带参数的类的初始化,如果不加双下划线,以上程序会报错:提示object() takes no parameter。)
(5):定义类中的函数时,必须包含形参self,且必须放在其它形参前。self是一个指向实例(instance)本身的引用,它让实例能够访问类中的属性和方法。(相当于C++的this指针)
这里self就是指类本身,self.name就是Dog类的属性变量,是Dog类所有。而name是外部传来的参数,不是Dog类所自带的。故,self.name = name 的意思就 是把外部传来的参数name的值赋值给Student类自己的属性变量self.name。(https://blog.csdn.net/CLHugh/article/details/75000104)

9.1.2 根据对象创建实例

1.访问属性

  1. __init__()方法的定义中的self.variable,就是类的属性变量。<br /> (self.variable就把它当作一个变量,而非像C++那样是一个调用。这个变量属于它的类。但在创建了实例后从外部访问时,变量名就是variable,而非self.variable)
  1. my_dog = Dog('willie', 6)
  2. my_dog.name

2.调用方法

  1. my_dog.sit()

9.2 使用类和实例

9.2.1 Car类

(给个列子,以便分析接下来的内容)

  1. class Car():
  2. """ 一次模拟汽车的简单尝试 """
  3. def __init__(self, make, model, year):
  4. """ 初始化描述汽车的属性 """
  5. self.make = make
  6. self.model = model
  7. self.year = year
  8. def get_descriptive_name(self):
  9. """ 返回整洁的描述性信息 """
  10. long_name = str(self.year) + ' ' + self.make + ' ' + self.model
  11. return long_name.title()
  12. my_new_car = Car('audi', 'a4', 2016)
  13. print(my_new_car.get_descriptive_name())

9.2.2 给属性指定默认值

  1. 类中的每个属性都必须有初始值,__init__()中直接将属性赋值(初始化)就好,无需再通过形参,等到创建实例时再传入初始化的值。
  1. def __init__(self, make, model, year):
  2. """ 初始化描述汽车的属性 """
  3. self.make = make
  4. self.model = model
  5. self.year = year
  6. self.odometer_reading = 0
  7. #直接对odometer_reading指定默认值将其初始化,无需再在__init__()中设置新的形参。

9.2.3 修改属性的值

1. 直接修改(直接从实例中访问属性赋新值)

  1. my_new_car.odometer_reading = 23

2. 通过方法修改

在Car类中新增一个函数(方法),实例通过调用这个函数对属性赋新值

  1. class Car():
  2. #省略部分代码,代码同9.2.1--
  3. def update_odometer(self, mileage):
  4. self.odometer_reading = mileage
  5. my_new_car.update_odometer(23) #调用函数修改odometer_reading的值

如果想让odometer递增 eg. self.odometer_reading += miles

9.3 继承

9.3.1 子类继承父类的属性和方法

  1. eg. 一个完整的父类(书P147, pdf84
  1. #父类Car
  2. class Car():
  3. """ 一次模拟汽车的简单尝试 """
  4. def __init__(self, make, model, year):
  5. self.make = make
  6. self.model = model
  7. self.year = year
  8. self.odometer_reading = 0
  9. def get_descriptive_name(self):
  10. long_name = str(self.year) + ' ' + self.make + ' ' + self.model
  11. return long_name.title()
  12. def read_odometer(self):
  13. print("This car has " + str(self.odometer_reading) + " miles on it.")
  14. def update_odometer(self, mileage):
  15. if mileage >= self.odometer_reading:
  16. self.odometer_reading = mileage
  17. else:
  18. print("You can't roll back an odometer!")
  19. def increment_odometer(self, miles):
  20. self.odometer_reading += miles

子类继承

  1. #子类继承
  2. class ElectricCar(Car):
  3. def __init__(self, make, model, year):
  4. super().__init__(make, model, year)
  5. my_tesla = ElectricCar('tesla', 'model s', 2016)
  6. print(my_tesla.get_descriptive_name())
  7. print(my_tesla.odometer_reading)


1.创建子类时,父类必须包含在当前文件中,且位于子类之前。
2.定义子类时,在类名后的括号中指定父类的名称。
3.在子类的初始化属性的定义中,必须包含父类中的需要初始化的属性;super()是一个特殊函数,将子类和父类关联起来,使得子类实例包含父类的所有属性。

  1. def __init__(self, make, model, year):
  2. super().__init__(make, model, year)
  3. #继承父类中所有属性的定义,如果不写这句,就得把父类中这些属性的定义再写一遍
  1. 同样,可以在子类的初始化属性的定义中,新增其它属性:
  1. def __init__(self, make, model, year month):
  2. super().__init__(make, model, year)
  3. self.month = month

9.3.1小结:

继承相当于只继承了除init()外的所有方法,唯独没有包含初始化属性,所以得通过super()关联子类和父类,才能包含父类的所有属性。

9.3.2 Python 2.7中继承的语法稍有不同

9.3.3 给子类定义新属性和方法

  1. 新属性:在定义__init__()中新增属性变量就行<br /> 如果需要通过实例传参进行初始化,则将属性变量放在__init__()中作为形参,在后面的语句中写出传参的赋值语句即可;
  1. def __init__(self, make, model, year month): #month是新属性
  2. super().__init__(make, model, year)
  3. self.month = month #month传参的赋值语句
  1. <br /> 如果对属性变量直接设置默认值来初始化,则直接在def __init__()的函数体中写出;(即不需要从外部传参)
  1. def __init__(self, make, model, year):
  2. super().__init__(make, model, year)
  3. self.battery_size = 70
  1. 对于新方法method:在子类中直接定义新函数就行。<br />

9.3.4 重写父类的方法

  1. 对父类的属性进行重写,使用相同名字的属性变量,则Python将不会考虑之前父类的赋值,而采用新的赋值;<br /> 对父类的方法进行重写,使用相同的名字,Python会采用新的方法的定义,而忽略原来的定义。<br /> eg.(完整程序见第9章的测试程序)
  1. class Car():
  2. """ 一次模拟汽车的简单尝试 """
  3. def __init__(self, make, model, year):
  4. self.make = make
  5. self.model = model
  6. self.year = year
  7. self.odometer_reading = 0
  8. def get_descriptive_name(self):
  9. long_name = str(self.year) + ' ' + self.make + ' ' + self.model
  10. return long_name.title()
  11. def read_odometer(self):
  12. print("This car has " + str(self.odometer_reading) + " miles on it.")
  13. def update_odometer(self, mileage):
  14. if mileage >= self.odometer_reading:
  15. self.odometer_reading = mileage
  16. else:
  17. print("You can't roll back an odometer!")
  18. def increment_odometer(self, miles):
  19. self.odometer_reading += miles
  20. def gasoline_tank(self, size):
  21. print(size)
  22. # 子类
  23. class ElectricCar(Car):
  24. def __init__(self, make, model, year):
  25. super().__init__(make, model, year)
  26. self.odometer_reading = 100
  27. #对父类的属性进行重写,使用相同名字的属性变量,则Python将不会考虑之前父类的赋值,而采用新的赋值
  28. def gasoline_tank(self, size):
  29. #对父类的方法进行重写,使用相同的名字,Python会采用新的方法的定义,而忽略原来的定义。
  30. print("电动车没有油箱")
  31. my_tesla = ElectricCar('tesla', 'model s', 2016)
  32. print(my_tesla.get_descriptive_name())
  33. my_tesla.gasoline_tank(50)
  34. print(my_tesla.odometer_reading)
  35. # 输出结果
  36. >>> 2016 Tesla Model S
  37. >>> 电动车没有油箱
  38. >>> 100

9.3.5 将实例用作属性

  1. 如果类中的细节越来越多:即属性和方法太多,可以将类的一部分提取出来,将一个大类拆分成多个小类。<br /> 例如:将有关电池的属性和方法提取出来,作为一个单独的属性;<br /> 然后再定义Car的子类ElectricCar,并再它的属性的初始化定义中定义 self.battery = Battery()<br /> 即,将属性实例化<br /> 这样,当子类ElectricCar实例化了变量my_tesla后,可以访问my_tesla中的实例变量battery,来进一步访问或调用有关电池的属性或者方法<br /> 语句为 my_tesla.battery.describe_battery()<br /> #书P150,pdf86
  1. class ElectricCar(Car):
  2. def __init__(self, make, model, year):
  3. super().__init__(make, model, year)
  4. self.odometer_reading = 100
  5. #对父类的属性进行重写,这样print出来里程表的值不是之前的0,而是现在的100
  6. def gasoline_tank(self, size):
  7. #对父类的方法进行重写,这样调用gasoline_tank函数时,即使写了实参size的值,print出来的也是“电动车没有油箱”
  8. print("电动车没有油箱")
  9. class Battery():
  10. """ 一次模拟电动汽车电瓶的简单尝试 """
  11. def __init__(self, battery_size=70):
  12. #属性变量需要通过实例传参来初始化,但默认值为70,即如果创建实例时没有指定实参,则初始化的值默认为70
  13. """ 初始化电瓶的属性 """
  14. self.battery_size = battery_size
  15. def describe_battery(self):
  16. """ 打印一条描述电瓶容量的消息 """
  17. print("This car has a " + str(self.battery_size) + "-kWh battery.")
  18. class ElectricCar(Car):
  19. """ 电动汽车的独特之处 """
  20. def __init__(self, make, model, year):
  21. """初始化父类的属性,再初始化电动汽车特有的属性"""
  22. super().__init__(make, model, year)
  23. self.battery = Battery() #将battery这个属性变量初始化为一个Battery的实例
  24. my_tesla = ElectricCar('tesla', 'model s', 2016)
  25. print(my_tesla.get_descriptive_name())
  26. my_tesla.battery.describe_battery()

9.4 导入类

9.4.1 导入单个类

  1. 语法: from module_name import class_name #注意,类名要大写<br /> eg. from car import Car <br /> 这种情况下在调用类创建实例时,不需要指定这个类属于哪个模块<br />

9.4.2 一个模块中存储多个类

  1. 例如:父类Car和子类ElectricCar都存放在一个模块car中,这样只要从这个模块中导入子类ElectricCar,就能创建一个新的实例<br /> eg. from car import ElectricCar<br /> 注意:虽然没有导入父类Car,而子类是继承了父类的属性的,看上去在用子类创建实例的时候会报错;<br /> 但是由于子类和父类来自于同一个模块,所以在导入子类时,(貌似)是把所有与子类定义相关的东西都导入了进来。 (自己猜的原因,不太确定)<br /> (假想原因的来源:因为后面9.4.6中,如果父类和子类不在同一个模块,则如果想用子类创建实例,必须在子类所在的模块中先导入父类)<br />

9.4.3 从一个模块中导入多个类

  1. 语法: from module_name import class_name_1, class_name_2 #类名要大写,每个类之间用逗号分隔<br />

9.4.4 导入整个模块

  1. 导入整个模块,再用句点表示法访问需要的类。<br /> 这样的好处是,由于创建实例时,类前面都包含它所属的模块名,因此不会与当前文件中的任何变量名称发生冲突。<br /> eg. import car<br /> my_tesla = car.ElectricCar('tesla', 'model s', 2016)<br />

9.4.5 导入模块中的所有类

  1. 语法: from module_name import *<br /> 注意:不推荐采用这种方式,理由同第八章中导入模块中的所有函数,因为可能发生重名的错误。<br />

9.4.6 在一个模块中导入另一个模块 (书P157,pdf89)

  1. 如果一个模块依赖于另一个模块,则需要在该模块中导入它所依赖的模块。<br /> 例如:子类和父类分别属于不同的模块,而子类是依赖于父类的。<br /> 如果我想在一个新的程序文件中用该子类创建实例,光导入子类的模块是没用的,因为子类需要访问其父类,而子类的模块中并没有它的父类。<br /> 因此,子类的模块中必须导入父类。这样我们才能成功创建实例。
  1. #car.py #第一个模块car,方便起见,省略了类的定义
  2. class Car():
  3. #electric_car.py
  4. from car import Car
  5. class ElectricCar(Car):
  6. #my_car.py
  7. from electric_car import ElectricCar
  8. my_tesla = ElectricCar('tesla', 'model s', 2016) #这样才能成功创建实例

9.5 Python标准库

Python标准库是一组模块,已经实现定义好了,可供程序员直接导入并使用标准库中的任何函数和类。
例如:模块collections中的一个类:OrderedDict #有序字典
当通过这个“有序字典”的类创建实例后,添加键-值对的顺序也会被记录下来,而不像普通字典,只关心键-值的对应关系而不关心顺序。

第九章小结:

  1. 类名的命名要求:首字母大写,名字中每个单词的首字母大写,不要用下划线;实例名统一小写,在单词之间加下划线。
    2. 定义类时,最好包含一个文档字符串 “”” “”” ,描述类的功能;模块里最好也有文档字符串(docstring)。
    3. 类之间用两个空行分隔,函数之间用一个空行分隔
    4. 需要导入时,先导入所需的标准库模块,再导入其它模块

    补充内容:

    1.默认情况下,Python中的成员函数和成员变量都是公开的(public),在python中没有类似public,private等关键词来修饰成员函数和成员变量。
    但是,却存在保护变量,私有变量。
    其实,Python并没有真正的私有化支持,但可用下划线得到伪私有。
    因此,尽量避免定义以下划线开头的变量!!!!!!!!!!!!!!!

    python中的私有变量和私有方法仍然是可以访问的;访问方法如下:
    私有变量:实例.类名__变量名
    私有方法:实例.
    类名方法名()

    (1)_xxx “单下划线 “ 开始的成员变量叫做保护变量,意思是只有类实例和子类实例能访问到这些变量,
    需通过类提供的接口进行访问;不能用’from module import *’导入
    (2)
    xxx 类中的私有变量/方法名 (Python的函数也是对象,所以成员方法称为成员变量也行得通。),
    “ 双下划线 “ 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据。
    (3)xxx 系统定义名字,前后均有一个“双下划线” 代表python里特殊方法专用的标识,如 init()代表类的构造函数。
    ——————————-
    原文:https://blog.csdn.net/sxingming/article/details/52875125

    类属性和实例属性

    简单来说,类属性就是 类对象 所拥有的属性,它被 该类的所有实例对象 所共有。
    类属性可以使⽤ 类对象 或 实例对象 访问。

两者的区别

  1. 实例不能对类属性进行修改,只能修改实例属性,如果强制修改类属性则会在实例中创建一个新的同名实例属性;
  2. 类没有实例属性;
  3. 类属性用于记录某一项需要保持不变的数据;
  4. 类属性的好处是,无需在实例化对象的时候单独为属性开辟一个内存空间,也就是说实例对象可以直接在类中引用这个属性数据。

参考:https://blog.csdn.net/lindangdang/article/details/123821641

注意:一般情况下,不能出现类属性与实例属性同名的情况,实例属性的优先级,高于类属性

  1. class Student:
  2. grade = 1 # 类属性
  3. def __init__(self, name, sex):
  4. """初始化,实例属性"""
  5. self.name = name
  6. self.sex = sex
  7. @classmethod # 类方法
  8. def set_grade(cls, grade):
  9. cls.grade = grade
  10. student1 = Student('Jack', 'male')
  11. student2 = Student('Bob', 'male')
  12. print(student1.grade) # 1
  13. print(student2.grade) # 1
  14. # 可以通过调用类方法,修改类属性(在所有的实例中都生效)
  15. Student.set_grade(3)
  16. print(student1.grade) # 3
  17. print(student2.grade) # 3
  18. student1.set_grade(5)
  19. print(student1.grade) # 5
  20. print(student2.grade) # 5
  21. # 实例不能对类属性进行修改,只能修改实例属性,如果强制修改类属性则会在实例中创建一个新的同名实例属性;
  22. student1.grade = 7 # 其实是在student1实例对象中,新创建了一个self.grade
  23. print(student1.grade) # 7 由于实例属性的优先级,高于类属性,所以得到的结果是7(一般来说,不允许实例属性和类属性名字相同)
  24. print(student2.grade) # 5
  25. # 要想修改类属性,要么通过类方法,要么直接对类属性赋值
  26. Student.grade = 11
  27. print(student1.grade) # 7 虽然类属性修改成功了,但因为优先级问题,得到的结果仍然是7
  28. print(student2.grade) # 11
  29. # 可以用del清除掉student1的self.grade(7的引用计数被-1为0,触发垃圾回收机制,内存占用被释放 -- 不确定这个理解是否正确)
  30. del student1.grade
  31. print(student1.grade) # 11
  32. print(student2.grade) # 11

3.实例方法、类方法、静态方法

实例方法只能被实例对象调用;静态方法(由@staticmethod装饰的方法)、类方法(由@classmethod装饰的方法),可以被类或类的实例对象调用。

实例方法,第一个参数必须要默认传实例对象,一般习惯用self。
静态方法,参数没有要求。
类方法,第一个参数必须要默认传类,一般习惯用cls。

  1. class Test(object):
  2. def __init(self):
  3. self.name = 'aaron'
  4. def mytest(self):
  5. """实例方法"""
  6. print('aaa', self) # <__main__.Test object at 0x7f21141108b0>
  7. @staticmethod # 加静态方法的装饰器后,无论是实例调用,还是通过类调用,都不会传self或cls了
  8. def mytest2():
  9. """静态方法"""
  10. print('bbb')
  11. # #@staticmethod # 如果不加静态方法的装饰器,就是个一般方法
  12. # def mytest2():
  13. # """静态方法"""
  14. # print('bbb')
  15. @classmethod
  16. def mytest3(cls):
  17. """类方法"""
  18. print('ccc', cls) # <class '__main__.Test'>
  19. # @classmethod # 报错 mytest3() takes 0 positional arguments but 1 was given (加了类方法的装饰器后,无论是实例调用,还是通过类直接调用,默认第一个参数会传类本身的引用,即cls)
  20. # def mytest3(): # 所以 @classmethod 与定义方法时的形参cls 需要同时存在
  21. # """类方法"""
  22. # print('ccc')

可以看到奇怪的是,这三种方法,通过实例对象获取,和通过类直接获取,得到的类型是不同的

  1. obj = Test()
  2. # 获取实例方法,一个是method,一个是function
  3. print(type(obj.mytest)) # <class 'method'>
  4. print(type(Test.mytest)) # <class 'function'>
  5. # 获取静态方法,两个都是fuction
  6. print(type(obj.mytest2)) # <class 'function'>
  7. print(type(Test.mytest2)) # <class 'function'>
  8. # 获取类方法,两个都是method
  9. print(type(obj.mytest3)) # <class 'method'>
  10. print(type(Test.mytest3)) # <class 'method'>

然后执行如下的调用(ps:调用是加了括号的,获取只需要方法名)
发现Test.mytest() ,即通过类,调用实例方法时报错,这是为什么呢

  1. # 通过实例对象,调用3种方法
  2. obj.mytest() # aaa <__main__.Test object at 0x7f211413f4c0>
  3. obj.mytest2() # bbb
  4. obj.mytest3() # ccc <class '__main__.Test'>
  5. # 不实例化,直接通过类,调用3种方法
  6. Test.mytest() # 报错:mytest() missing 1 required positional argument: 'self' (mytest是实例方法,必须传self,所以只能通过实例对象调用)
  7. Test.mytest2() # bbb
  8. Test.mytest3() # ccc <class '__main__.Test'>

先来了解下method和fuction的定义
Method
A function which is defined inside a class body. If called as an attribute of an instance of that class, the method will get the instance object as its first argument (which is usually called self). See function and nested scope(嵌套作用域).

Function
A series of statements which returns some value to a caller. It can also be passed zero or more arguments which may be used in the execution of the body. See also parameter, method, and the Function definitions section.

显然,method在被调用时,会将它的实例对象作为第一个参数。

再回到刚才的执行结果,Test.mytest是一个function,而不是method,所以当它被调用时,不会默认传一个实例对象,所以报错『缺少了一个位置参数』。此时若任意传一个参数,例如Test.mytest(1),那么定义中的self其实就等于1,而不是实例对象,因为它不是method。
(注意,类方法在被调用时,传的参数是类本身,而不是实例对象)

类方法
由@classmethod装饰的方法,默认第一个参数为cls(装饰器与定义方法时的形参cls,需要同时存在)
使用场景:存在多个不同实例对象,需要一次性修改所有实例对象中的某个类属性时,可以通过类方法修改

  1. @classmethod
  2. def mytest3(cls):
  3. """类方法"""
  4. print('ccc', cls) # <class '__main__.Test'>
  5. # 若类方法定义时,没有加cls参数,调用时会报错。且obj.mytest3与Test.mytest3均为method
  6. # @classmethod # 报错 mytest3() takes 0 positional arguments but 1 was given (加了类方法的装饰器后,无论是实例调用,还是通过类直接调用,默认第一个参数会传类本身的引用,即cls)
  7. # def mytest3(): # 所以 @classmethod 与定义方法时的形参cls 需要同时存在
  8. # """类方法"""
  9. # print('ccc')
  10. # print(type(obj.mytest3)) # <class 'method'>
  11. # print(type(Test.mytest3)) # <class 'method'>

静态方法
由@classmethod装饰的方法
使用场景:不涉及实例和类的逻辑,可以放在静态方法中(可以当做普通函数)

  1. @staticmethod # 加静态方法的装饰器后,无论是实例调用,还是通过类调用,都不会传self或cls了
  2. def mytest2():
  3. """静态方法"""
  4. print('bbb')
  5. # 如果不加静态方法的装饰器,就是个普通的实例方法;obj.mytest2是method,Test.mytest2是function
  6. # #@staticmethod
  7. # def mytest2():
  8. # """静态方法"""
  9. # print('bbb')
  10. # print(type(obj.mytest2)) # <class 'method'>
  11. # print(type(Test.mytest2)) # <class 'function'>