类是对象的蓝图和模板,而对象是类的实例。类是抽象的模板,比如Student类,而实例是根据类创建出来的一个个具体的“对象”,每个对象都拥有相同的方法,但各自的数据可能不同。

定义类

  1. class Student(object):
  2. def __init__(self,name,age):
  3. self.name = name
  4. self.age = age
  5. def info(self):
  6. print('%s: %s' %(self.name,self.age))
  7. if __name__ == '__main__':
  8. student = Student('zxy','18')
  9. student.info()
  • init是一个特殊方法用于在创建对象时进行初始化操作,通过这个方法为学生对象绑定name和age两个属性
  • 注意到init方法的第一个参数永远是self,表示创建的实例本身,因此,在init方法内部,就可以把各种属性绑定到self,因为self就指向创建的实例本身。
  • 有了init方法,在创建实例的时候,就不能传入空的参数了,必须传入与init方法匹配的参数,但self不需要传,Python解释器自己会把实例变量传进去

    1. student = Student('zxy','18')
  • 写在类中的函数,我们通常称之为(对象的)方法,这些方法就是对象可以接收的消息。程序中info方法打印学生信息

小结

  • 类是创建实例的模板,而实例则是一个一个具体的对象,各个实例拥有的数据都互相独立,互不影响;
  • 方法就是与实例绑定的函数,和普通函数不同,方法可以直接访问实例的数据;
  • 通过在实例上调用方法,我们就直接操作了对象内部的数据,但无需知道方法内部的实现细节。
  • 和静态语言不同,Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同

访问限制

实际开发过程中并不建议将属性私有化!

从前面Student类的定义来看,外部代码还是可以自由地修改一个实例的name、age属性

  1. class Student(object):
  2. def __init__(self,name,age):
  3. self.name = name
  4. self.age = age
  5. if __name__ == '__main__':
  6. student = Student('zxy','18')
  7. old = student.age
  8. print('old:',old)
  9. student.age = 23
  10. new = student.age
  11. print('new:',new)
  12. # 结果:
  13. old: 18
  14. new: 23

在Python中,属性和方法的访问权限只有两种,也就是公开的和私有的,如果希望属性是私有的,在给属性命名时可以用两个下划线作为开头,在Python中,实例的变量名如果以__(双下划线)开头,就变成了一个私有变量(private),只有内部可以访问,外部不能访问。

改完后,对于外部代码来说,没什么变动,但是已经无法从外部访问实例变量.name和实例变量.age

  1. class Student(object):
  2. def __init__(self,name,age):
  3. self.__name = name
  4. self.__age = age
  5. if __name__ == '__main__':
  6. student = Student('zxy','18')
  7. old = student.__age
  8. print('old:',old)
  9. # 报错
  10. old = student.__age
  11. AttributeError: 'Student' object has no attribute '__age'

这样就确保了外部代码不能随意修改对象内部的状态,这样通过访问限制的保护,代码更加健壮。但是如果外部代码要获取name和score怎么办?可以给Student类增加get方法:

  1. class Student(object):
  2. def __init__(self,name,age):
  3. self.__name = name
  4. self.__age = age
  5. def get_name(self):
  6. return self.__name
  7. def get_age(self):
  8. return self.__age
  9. if __name__ == '__main__':
  10. student = Student('zxy','18')
  11. old = student.get_age()
  12. print('old:',old)
  13. # 结果
  14. old: 18

如果又要允许外部代码修改score怎么办?可以再给Student类增加set方法:

  1. class Student(object):
  2. def __init__(self,name,age):
  3. self.__name = name
  4. self.__age = age
  5. def get_name(self):
  6. return self.__name
  7. def get_age(self):
  8. return self.__age
  9. def set_name(self,name):
  10. self.__name = name
  11. def set_age(self,age):
  12. self.__age = age
  13. if __name__ == '__main__':
  14. student = Student('zxy','18')
  15. old = student.get_age()
  16. print('old:',old)
  17. # 使用set方法修改
  18. student.set_age(23)
  19. # 使用get方法获取
  20. new = student.get_age()
  21. print('new:',new)
  22. # 结果
  23. old: 18
  24. new: 23

那么就有疑问了:原先那种直接通过student.age= 23也可以修改啊,为什么要定义一个方法大费周折?因为在方法中,可以对参数做检查,避免传入无效的参数:

  1. class Student(object):
  2. def __init__(self, name, age):
  3. self.__name = name
  4. self.__age = age
  5. def get_name(self):
  6. return self.__name
  7. def get_age(self):
  8. return self.__age
  9. def set_name(self, name):
  10. self.__name = name
  11. def set_age(self, age):
  12. if isinstance(age,int):
  13. self.__age = age
  14. else:
  15. print('传入的数据不合法!')
  16. if __name__ == '__main__':
  17. student = Student('zxy', '18')
  18. old = student.get_age()
  19. print('old:', old)
  20. # 使用set方法修改
  21. student.set_age(23.4)
  22. # 使用get方法获取
  23. new = student.get_age()
  24. print('new:', new)
  25. # 结果
  26. old: 18
  27. 传入的数据不合法!
  28. new: 18

可以发现当传入的参数不合法时,并不会修改属性值

注意

  1. 变量名类似xxx的,也就是以双下划线开头,并且以双下划线结尾的,是特殊变量,特殊变量是可以直接访问的,不是private变量,所以,不能用namescore这样的变量名。
  2. 些时候,你会看到以一个下划线开头的实例变量名,比如_name,这样的实例变量外部是可以访问的,但是,按照约定俗成的规定,当你看到这样的变量时,意思就是,“虽然我可以被访问,但是,请把我视为私有变量,不要随意访问”。
  3. 在实际开发中,我们并不建议将属性设置为私有的,因为这会导致子类无法访问。所以大多数Python程序员会遵循一种命名惯例就是让属性名以单下划线开头来表示属性是受保护的,本类之外的代码在访问这样的属性时应该要保持慎重。这种做法并不是语法上的规则,单下划线开头的属性和方法外界仍然是可以访问的,所以更多的时候它是一种暗示或隐喻 ```python class Student(object): def init(self, name, age):
    1. self._name = name
    2. self._age = age

if name == ‘main‘: student = Student(‘zxy’, ‘18’) old = student._name print(old)

结果

zxy

<a name="gkNfq"></a>
# 案例:工资结算系统
某公司有三种类型的员工 分别是部门经理、程序员和销售员 需要设计一个工资结算系统 根据提供的员工信息来计算月薪 部门经理的月薪是每月固定15000元 程序员的月薪按本月工作时间计算 每小时150元 销售员的月薪是1800元的底薪加上销售额5%的提成

---

<a name="XVYB7"></a>
## super().__init__()用法
做这个案例前,需要了解一个知识:Python子类构造函数调用super().__init__()用法说明<br />先看下面代码:
```python
class Employee():
    def __init__(self, name):
        self.name = name


class Manager(Employee):
    pass

class Programmer(Employee):
    def __init__(self,age):
        self.age = age

class Salesman(Employee):
    def __init__(self,name,age):
        self.age = age
        super().__init__(name)


m = Manager('小明')
print(m.name)
print('===========')

p = Programmer(10)
print(p.age)
# print(p.name)   # 报错'Programmer' object has no attribute 'name'
print('===========')

s = Salesman('小红',15)
print(s.name)
print(s.age)

运行结果:

小明
===========
10
===========
小红
15

[总结] super的作用:

  1. 如果子类(Manager)继承父类(Employee),不做初始化(即不使用__init__函数),那么子类(Manager)会自动继承父类(Employee)的属性(name)
  2. 如果子类(Manager)继承父类(Employee)做了初始化,且不调用super初始化父类构造函数,那么子类(Manager)不会自动继承父类的属性(name)
  3. 如果子类(Manager)继承父类(Employee)做了初始化,且调用了super初始化了父类的构造函数,那么子类(Manager)也会继承父类的(name)属性。

工厂模式

参考:https://codeantenna.com/a/iFPgcZqezs

  1. 所谓”工厂模式“就是专门提供一个”工厂类“去创建对象,我只需要告诉这个工厂我需要什么,它就会返回给我一个相对应的对象。
  2. 如果要创建一系列复杂的对象,需要提供很多的创建信息;或者是我想要隐藏创建对象时候的“代码逻辑”,就需要使用“工厂模式”
  3. 作为一种创建类模式,在任何需要生成复杂对象的地方,都可以使用工厂方法模式。有一点需要注意的地方就是复杂对象适合使用工厂模式,而简单对象,特别是只需要通过 new 就可以完成创建的对象,无需使用工厂模式。如果使用工厂模式,就需要引入一个工厂类,会增加系统的复杂度。

了解到上面知识后,我们可以编写此案例的代码了

"""
月薪结算系统 - 部门经理每月15000 程序员每小时200 销售员1800底薪加销售额5%提成
"""

from abc import ABCMeta, abstractmethod


class Employee(metaclass=ABCMeta):
    def __init__(self, name):
        self.name = name

    @abstractmethod
    def get_salary(self):
        """月薪结算抽象方法(子类必须重写)"""
        pass


class Manager(Employee):
    """部门经理"""

    def get_salary(self):
        return 15000.0


class Programmer(Employee):
    """程序员"""

    def __init__(self, name, working_hour):
        self.working_hour = working_hour
        # 子类继承父类Employee,且调用了super初始化父类的构造函数,那么子类也会继承父类的name属性
        super().__init__(name)

    def get_salary(self):
        return self.working_hour * 200.0


class Salesman(Employee):
    """销售员"""

    def __init__(self, name, sale, rate=0.05):
        self.sale = sale
        self.rate = rate
        super().__init__(name)

    def get_salary(self):
        return self.sale * self.rate + 1800.0


class EmployeeFactory:
    """创建员工的工厂(工厂模式 - 通过工厂实现对象使用者和对象之间的解耦合)"""

    @staticmethod
    def create(emp_type, *args, **kwargs):
        """创建员工"""
        all_emp_types = {'M': Manager, 'P': Programmer, 'S': Salesman}
        cls = all_emp_types[emp_type.upper()]
        return cls(*args, **kwargs) if cls else None


def main():
    """主函数"""
    emps = [
        EmployeeFactory.create('M', '曹操'),
        EmployeeFactory.create('P', '郭嘉', 85),
        EmployeeFactory.create('S', '典韦', 123000),
    ]
    for emp in emps:
        print(f'{emp.name}: {emp.get_salary():.2f}元')


if __name__ == '__main__':
    main()
  1. 上面使用到工厂模式[ https://codeantenna.com/a/iFPgcZqezs]
  2. cls = all_emp_types[emp_type.upper()]返回的是字典all_emp_types中对象
  3. return cls(*args, **kwargs) if cls else None:返回对象携带剩余参数(如:创建是EmployeeFactory.create('P', '郭嘉', 85),返回Programmer('郭嘉', 85)

运行结果:

曹操: 15000.00元
郭嘉: 17000.00元
典韦: 7950.00元