Python基础(十二)—面向对象拾遗(__slots__、@property、枚举类、元类)

编译型语言和解释型语言

  • 编译型语言
    • 定义
      需通过编译器(compiler)将源代码编译成机器码,之后才能执行的语言。一般需经过编译(compile)、链接(linker)这两个步骤。
      • 编译是把源代码编译成机器码
      • 链接是把各个模块的机器码和依赖库串连起来生成可执行文件
    • 优缺点
      • 优点:编译器一般会有预编译的过程对代码进行优化。因为编译只做一次,运行时不需要编译,所以编译型语言的程序执行效率高。可以脱离语言环境独立运行。
      • 缺点:编译之后如果需要修改就需要整个模块重新编译。编译的时候根据对应的运行环境生成机器码,不同的操作系统之间移植就会有问题,需要根据运行的操作系统环境编译不同的可执行文件。
    • 代表语言
      C、C++、Pascal、Object-C以及最近很火的苹果新语言swift
  • 解释型语言
    • 定义
      解释性语言的程序不需要编译,相比编译型语言省了道工序,解释性语言在运行程序的时候才逐行翻译。
    • 优缺点:
      • 优点:有良好的平台兼容性,在任何环境中都可以运行,前提是安装了解释器(虚拟机)。灵活,修改代码的时候直接修改就可以,可以快速部署,不用停机维护。
      • 缺点:每次运行的时候都要解释一遍,性能上不如编译型语言。
    • 代表语言
      JavaScript、Python、Erlang、PHP、Perl、Ruby

动态语言和静态语言

  • 静态类型语言
    • 定义
      类型判断是在运行前判断(如编译阶段),比如C#、java就是静态类型语言,静态类型语言为了达到多态会采取一些类型鉴别手段,如继承、接口
    • 优缺点
      优点:结构非常规范,便于调试,方便类型安全;缺点:为此需要写更多的类型相关代码,导致不便于阅读、不清晰明了。
  • 动态语言
    • 定义
      程序在运行时可以改变其结构:新的函数可以被引进,已有的函数可以被删除等在结构上的变化,类型的检查是在运行时做的。
    • 优缺点
      优点:方便阅读,不需要写非常多的类型相关的代码;缺点:不方便调试,命名不规范时会造成读不懂,不利于理解等。
  • 关于弱类型、强类型、动态类型、静态类型语言的划分
    类型划分.png

使用__slots__

限制实例的属性

  • 动态语言动态绑定属性和方法
    当定义了一个class,创建实例后,我们可以给该实例绑定任何属性和方法,这就是动态语言的灵活性,绑定方式及效果请看以下代码

对实例绑定的属性或方法只对绑定实例有效,对另外实例无效。采用给class绑定,则对所有实例均有效。

class Student:
    pass

s1 = Student()
s1.name = 'lz'
print(s1.name)  # lz

s2 = Student()
# print(s2.name)  # 报错:AttributeError: 'Student' object has no attribute 'name'

def set_age(self, age):
    self.age = age
from types import MethodType
s1.set_age = MethodType(set_age, s1)
s1.set_age(17)
print(s1.age)  # 17,对s2不起作用

# 对实例绑定的属性或方法只对绑定实例有效,对另外实例无效。采用给class绑定,则对所有实例均有效。
Student.set_age = MethodType(set_age ,Student)
s3 = Student()
s3.set_age(20)
print(s3.age)  # 20

  • 使用__slots__限制实例的属性
    如果想要限定实例的属性,可以使用__slots__对类属性进行限制,但是只对当前类的实例有效,对其子类无效。
class Fish:
    __slots__ = ('name', 'age', 'sex')  # 如果这里没有sex,则下面的print会报错没有该属性

    def __init__(self, sex):
        self.sex = sex

f1 = Fish('man')
print(f1.sex)

# f1.size = 18  # 报错没有该属性

@property

@property广泛应用于类的定义中,可以简化代码,同时保证对参数进行必要的检查,检查程序运行的出错。

在Python基础(十)—面向对象的深入讲解(继承、Mixin编程机制等)章节中,说的是property(fget=None, fset=None, fdel=None, doc=None):通过类定义的属性设置属性。和@property有本质区别.

  • 范围限定
    例如我们新建Student,在设置成绩sorce属性上,我们是要对sorce进行范围限定的,这个就需要在class中的set方法中进行相对的限定,如下代码:
class Student:
    def get_sorce(self):
        return self.sorce
    def set_sorce(self, value):
        if not isinstance(value, int):
            raise ValueError('sorce必须是Int')
        if value < 0 or value > 100:
            raise ValueError('sorce必须介于0~100')
        self._sorce = value

单下划线和双下划线都是限定属性的私有

  • 使用@property装饰器讲方法变为属性
    @property可以使方法变为属性的方式进行访问,以上代码修改为:
class Student:
  @property
  def sorce(self):
      return self.sorce

  @sorce.setter
  def set_sorce(self, value):
      if not isinstance(value, int):
          raise ValueError('sorce必须是Int')
      if value < 0 or value > 100:
          raise ValueError('sorce必须介于0~100')
      self._sorce = value
#把一个getter方法加上@property,就变成了属性,同时@property本身又创建了另一个装饰器@sorce.setter,负责把一个setter方法变成属性赋值,这样就拥有了一个可控的属性操作。
  • 利用@property进行属性的读写限制
    如果之定义getter方法,而不定义setter方法,那该属性则是只读的,从以下方法可看出,age不需要可写,只需当前时间减去birth即可,因此设置为只读。
class Man:
  @property
  def birth(self):
      return self._birth
  @birth.setter
  def birth(self, value):
      self._birth = value
  
  @property
  def age(self):
      return 2019-self._birth
  }

使用枚举类

  • 使用枚举类的好处
    对于一些常量(星期,月份等),使用不可变的枚举类,即增加了可读性,也不用担心出错,并且枚举类在内存中存放的是数字,也提高了程序效率,同时成员还可以直接比较。
  • 创建枚举类
    枚举类型可以通过继承Enum类来实现,注意Enum类是在enum模块中的。
from  enum import Enum
class VIP(Enum):
    YELLOW = 1
    RED = 2
    BLUE = 3
    ```
  * 查看枚举类型
    枚举类型是一个特殊的类,我们可以查看它的名称和值。
```javascript
print(VIP.YELLOW) #枚举类型
print(VIP['YELLOW']) #枚举类型
print(VIP.YELLOW.name) #枚举名称
print(VIP.YELLOW.value)  #枚举值
print(VIP(3)) #数字得到枚举类型

#遍历
for v in VIP:  #遍历
    print(v)

# 成员的value默认从1开始
for name, member in VIP.__members__.items():
    print(name, "->", member, ',', member.value)
"""
YELLOW -> VIP.YELLOW , 1
RED -> VIP.RED , 2
BLUE -> VIP.BLUE , 3
"""
  • 重复和唯一的枚举类型
    当存在枚举成员的名称有重复时,则后面的枚举成员相当于第一个枚举成员的别名,而且在实际使用中,就是使用的第一次出现的枚举成员。
from enum import Enum, unique
@unique
class Mistake(Enum):
    ONE = 1
    TWO = 2
    THREE = 3
    FOUR = 3
"""
出错:ValueError: duplicate values found in <enum 'Mistake'>: FOUR -> THREE
"""
  • 枚举类型的比较
    枚举类型不能做大小比较,但是可以做身份比较和等值比较。枚举类型没有定义比较运算符,通常不能进行大小比较(不过,继承“IntEnum"类的枚举类型可以进行大小比较,他们的枚举值只能是整数)。

枚举类型是使用单例模式实现的。在创建枚举类的时候,Python就在内存中为我们创建了枚举类的对象,因此我们不必实例化枚举类。并且由于枚举类的“new”方法,将会保证内存中只会存在一个枚举类的实例。

type()创建类

动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是在运行时动态创建的。

  • 假如我们定义了Hello的class,并保存为hello.py.当Python解释器载入hello.py模块时,会依次执行该模块的所有语句,执行的结果就是动态地创建出一个hello的class对象,测试如下(type()可以查看一个类型或变量的类型):
class Hello(object):
    def hello(self, name='world'):
        print('hello %s.' % name)

h = Hello()
print(type(Hello))  # <class 'type'>
print(type(h))   # <class '__main__.Hello'>

# Hello是一个class,类型为type,h是一个实例,它的类型就是class Hello
  • type()不仅可以查看一个对象的类型,还可以使用type()函数创建一个class,以下使用type()创建上面的Hello类,无需使用class
def hl(self, name='world'):
    print('hello %s.' % name)
Hello = type('Hello', (object,), dict(hello = hl))  # 创建Hello的类

h = Hello()
h.hello()
print(type(Hello))
print(type(h))
"""
hello world.
<class 'type'>
<class '__main__.Hello'>
"""
  • 使用type()创建类的三个参数介绍
    • class的名称;
    • 继承的父类集合:tuple的格式(单元素写法),支持多重继承;
    • class的方法名称与函数绑定

通过type()函数创建类与直接写class是完全一样的,因为Python解释器遇到class定义时,仅是扫描class定义的语法,然后调用type()进行创建class,这也就是动态语言支持运行期间创建类。

metaclass 元类

  • metaclass的解释
    • 当我们定义了类之后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例
    • 如果我们想创建出类,那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类
    • 因此连接起来就是:先定义metaclass,就可以创建类,然后创建实例。

所以 metaclass 允许你创建类或修改类,也就是你可以把类看成是 metaclass 创建出来的“实例”。

  • 实际运用很少,后续略…

个人博客:Loak 正 - 关注人工智能及互联网的个人博客
文章地址:Python基础(十二)—面向对象拾遗(_slots_、@property、枚举类、元类)

原文地址:https://www.cnblogs.com/l0zh/p/13739750.html