class 类
Table of Contents

类之间的关系

类和类之间的关系有三种:is-a、has-a和use-a关系

使用一种叫做UML(统一建模语言)的东西来进行面向对象建模,其中一项重要的工作就是把类和类之间的关系用标准化的图形符号描述出来。

利用类之间的这些关系,我们可以在已有类的基础上来完成某些操作,也可以在已有类的基础上创建新的类,这些都是实现代码复用的重要手段。

img

类的定义

类是现实世界或者思维世界中的实体在计算机中的反映

它将数据以及这些数据上的操作封装在一起

class ClassName:
    pass

# 等价于

class ClassName(object):
    pass
class Person:          #定义一个类Person
    def SayHi(self):   #类成员函数必须要有一个参数self,表示类的实例(对象)自身
        print('Hi')

p = Person()           #定义类Person的对象p, 用p来访问类的成员变量和成员函数
p.SayHi()              #调用类Person的成员函数SayHi() 
>>>
hi

定义类属性

class A:

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

a = A(5)
a.name = True
print(a.name)
>>>
True

方法定义 (类中的函数)

写在类中的函数,我们通常称之为(对象的)方法

class ClassName:
    def method_name(self):
        pass

相对于普通函数,需要传入self参数

class A:
    def print_file(self):
        pass

实例方法

是和对象实例相关联的,是实例可以调用的

用于描述类的行为

操作类变量

也可以来操作类变量, 但类变量有专门操作的classmethod

class Student():
    sum1 = 0

    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__class__.sum1 += 1
        print("Total students is " + str(self.__class__.sum1))


student = Student('Rick', 18)
student = Student('Michelle', 18)
student = Student('Sam', 18)
>>>
Total students is 1
Total students is 2
Total students is 3

私有方法 (双下划线开头)

__val 但不以__结尾的

__private_method:两个下划线开头,声明该方法为私有方法,不能在类地外部调用。在类的内部调用 self.__private_methods

class A:
    def __init__(self, x):
        self.__val = x

    def __add(self, i):
        self.__val += i

    def get_val(self):
        return self.__val

    def increase(self):
        self.__add(1)

a = A(5)

a.__add
>>>
Traceback (most recent call last):
  File "/Users/xhxu/python/python3/test/10.py", line 15, in <module>
    a.__add()
AttributeError: 'A' object has no attribute '__add'
class A:
    def __init__(self, x):
        self.__val = x

    def __add(self, i):
        self.__val += i

    def get_val(self):
        return self.__val

    def increase(self):
        self.__add(1)

a = A(5)

print(a.get_val())
>>>
5

在类内部对行为进行封装, increase 对__add封装

class A:
    def __init__(self, x):
        self.__val = x

    def __add(self, i):
        self.__val += i

    def get_val(self):
        return self.__val

    def increase(self):
        self.__add(1)

a = A(5)
print(a.get_val())

a.increase()
print(a.get_val())
>>>
5
6

私有方法原理

python并没有真正的私有,变量改名 _classname__val来绕过

class A:
    def __init__(self, x):
        self.__val = x

    def __add(self, i):
        self.__val += i

    def get_val(self):
        return self.__val

    def increase(self):
        self.__add(1)

a = A(5)
print(a.get_val())

a.increase()
print(a.get_val())

print(dir(a))
print(a._A__val)

print(a.__dict__)

>>>
5
6
['_A__add', '_A__val', '__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__', 'get_val', 'increase']
6
{'_A__val': 6}
class Student():
    sum1 = 0

    def __init__(self, name, age):
        self.name = name
        self.age = age
        self.__score = 0
        self.__class__.sum1 += 1
        print("Total students is " + str(self.__class__.sum1))

    def marking(self, score):
        self.score = score
        if self.score < 0:
            print('Invalid num')
        else:
            print('score', self.score)


    def do_homework(self):
        pass

    def do_english_homework(self):
        pass

student = Student('Rick', 18)
student = Student('Michelle', 18)
student1 = Student('Sam', 18)
result = student.marking(-1)
student1.__score = -1
print(student1.__dict__)
print(student1._Student__score)

>>>
Total students is 1
Total students is 2
Total students is 3
Invalid num
{'age': 18, '__score': -1, 'name': 'Sam', '_Student__score': 0}
0

这里将__score 的私有变量重新命名成_Student__score 这种类名+score 的形式

但是可以强行读取到数值

self

self就是当前调用某一个方法的对象, self 代表的是一个实例,而不是一个类

可以在类中定义成为任意的标识,比如this, 但约定统一使用self

class A:
    name = 'Rick'
    age = 18

    def __init__(this, name, age):
        this.name = name
        this.age = age

类的属性

实例变量

定义在方法中的变量,只作用于当前实例的类。

实例变量的作用域,就是实例本身

类可以访问;类内部可以访问;派生类中可以访问

class C:

    def __init__(self):
        self.var = 'public var'

    def func(self):
        print(self.var)


class D(C):

    def show(self):
        print(self.var)

obj = C()
obj.var   # 对象的属性引用
obj.func()

obj_son = D()
obj_son.show()
>>>
public var
public var

保护变量(单下划线开头)

"单下划线" 开始的成员变量叫做保护变量,意思是只有类对象和子类对象自己能访问到这些变量

不过根据python的约定,应该将其视作private,而不要在外部使用它们

私有变量 (双下划线开头)

"双下划线" 开始的是私有成员,意思是只有类对象自己能访问,连子类对象也不能访问到这个数据

仅类内部可以访问, 无法被外部调用修改

都是在init方法中定义的

class C:

    def __init__(self):
        self.__var = 'private var'

    def func(self):
        print(self.__var)


class D(C):

    def show(self):
        print(self.__var)

obj = C()
obj.__var # 通过对象访问    ==> 错误
obj.func() # 类内部访问        ==> 正确

obj_son = D()
obj_son.show() # 派生类中访问  ==> 错误

类变量

类变量对所有的实例都是可见的,可以共享,且初值都是一样的

定义在方法体之外,可以通过类名访问,对所有方法共享

实例变量没有,会找类变量调用

类变量通常不作为实例变量使用, 即类变量不用做定义实例变量

也称为数据成员,用于描述刻画类的特征

class A:
    val = 3
    def __init__(self, x):
        self.x = 3

a1 = A(5)
print(a1.val)

a2 = A(9)
print(a2.val)

a1.val += 1
print(a1.val)
>>>
3
3
4

对不可变对象赋值,就会变成新的变量

class A:
    val = [1, 2, 3]
    val_s = 'a'

    def __init__(self):
        pass

a1 = A()
a2 = A()
a1.val.append(4)
print(a1.val, id(a1.val))
print(a2.val, id(a2.val))

a3 = A()
a4 = A()
a3.val_s = 's'
print(a3.val_s, id(a3.val_s))
print(a4.val_s, id(a4.val_s))
>>>
[1, 2, 3, 4] 4442997640
[1, 2, 3, 4] 4442997640
s 4437293632
a 4437455120

在类内部中的实例方法内,访问类变量的方式

class Student():
    sum1 = 0

    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(Student.sum1)


student = Student('Rick', 18)
>>>
0

或者通过内置变量__class__访问类变量的值

class Student():
    sum1 = 0

    def __init__(self, name, age):
        self.name = name
        self.age = age
        print(self.__class__.sum1)


student = Student('Rick', 18)
>>>
0

私有类变量 (双下划线开头)

私有类变量不能被实例访问

类内部可以通过方法访问

__private_attrs:两个下划线开头,声明该属性为私有,不能在类的外部被使用或直接访问。在类内部的方法中使用时 self.__private_attrs

class C:
    __name = 'private var'

    def func(self):
        print('func in C', C.__name)


class D(C):

    def show(self):
        print('show in D', C.__name)

C.__name # 类访问 错误
obj = C()
obj.func() # 类内部可以访问

obj_son = D()
obj_son.show() # 派生类中访问错误

类及变量权限访问

类能否访问 实例能否访问 类是否能够通过公有类方法调用私有变量 实例是否能够通过公有实例方法调用私有变量
私有类变量 × ×
私有类方法 × × - -
私有实例变量 × × ×
私有实例方法 × × - -
公有类变量
公有类方法 - -
公有实例变量 × ×
公有实例方法 × - -

实例化

__init__ 只是初始化类变量的作用

class A:
    def __init__(self, x):
        self.x = x

# 实例化
a = A(5)

# 绑定实力变量
print(a.x)
>>>
5

实例化类原理

class A:
    def __new__(cls, *args, **kwargs):
        print("call __new__")
        print("type cls", type(cls))
        return object.__new__(cls)

    def __init__(self, x):
        print("call __init__")
        print("type self", type(self))
        s1 = set (dir(self))
        self.x = x
        s2 = set(dir(self))
        print(s2 - s1)


a = A(5)
>>>
call __new__
type cls <class 'type'>
call __init__
type self <class '__main__.A'>
{'x'}

cls 代表class A这个对象

dir 会返回所有属性

若new方法不返回,init方法不执行

class A:
    def __new__(cls, *args, **kwargs):
        print("call __new__")
        print("type cls", type(cls))
        # return object.__new__(cls)

    def __init__(self, x):
        print("call __init__")
        print("type self", type(self))
        s1 = set (dir(self))
        self.x = x
        s2 = set(dir(self))
        print(s2 - s1)


a = A(5)
>>>
call __new__
type cls <class 'type'>

封装

封装只能在类的内部访问

class A:
    def __init__(self, x):
        self.__value = x

    def __add(self):
        self.__value += i

    def get_value(self):
        return self.__value

    def __increase(self):
        self.__add(1)

a = A(5)
print(a.get_value())
print(a.__value)
>>>
5
Traceback (most recent call last):
  File "/Users/xhxu/python/python3/test/3.py", line 17, in <module>
    print(a.__value)
AttributeError: 'A' object has no attribute '__value'

数据及行为封装

class Door():
    def __init__(self, number, status):
        self.number = number
        self.status = status

    def open(self):
        self.status = 'opening'

    def close(self):
        self.status = 'closed'

door1 = Door(1, 'closed')
from time import sleep

class Clock(object):
    """My Clock"""

    def __init__(self, hour=0, minute=0, second=0):
        """Initial method
        :param hour: hour:
        :param minute: minute
        :param second: second
        """
        self._hour = hour
        self._minute = minute
        self._second = second

    def run(self):
        """Working on Clock
        """
        self._second += 1
        if self._second == 60:
            self._second = 0
            self.minute += 1
            if self._minute == 60:
                self._minute = 0
                self._hour += 1
                if self._hour == 24:
                    self._hour = 0

    def show(self):
        """Display time
        """
        return '%02d:%02d:%02d' % \
            (self._hour, self._minute, self._second)

def main():
    clock = Clock(23, 59, 48)
    while True:
        print(clock.show())
        sleep(1)
        clock.run()


if __name__ == "__main__":
    main()    

type 类

类 是由 type 类实例化产生

type 有解释器封装生成

def func(self):
    print 'hello wupeiqi'

Foo = type('Foo',(object,), {'func': func})
#type第一个参数:类名
#type第二个参数:当前类的基类
#type第三个参数:类的成员
def func(self):
    print("hello %s"%self.name)

def __init__(self,name,age):
    self.name = name
    self.age = age
Foo = type('Foo',(object,),{'func':func,'__init__':__init__})

f = Foo("jack",22)
f.func()

type类中实现的创建类

类中有一个属性 metaclass,其用来表示该类由 谁 来实例化创建,所以,我们可以为 metaclass 设置一个type类的派生类,从而查看 类 创建的过程。

img

元类 metaclass

类的生成 调用 顺序依次是 __new__ --> __init__ --> __call__

class MyType(type):
    def __init__(self,*args,**kwargs):

        print("Mytype __init__",*args,**kwargs)

    def __call__(self, *args, **kwargs):
        print("Mytype __call__", *args, **kwargs)
        obj = self.__new__(self)
        print("obj ",obj,*args, **kwargs)
        print(self)
        self.__init__(obj,*args, **kwargs)
        return obj

    def __new__(cls, *args, **kwargs):
        print("Mytype __new__",*args,**kwargs)
        return type.__new__(cls, *args, **kwargs)

print('here...')
class Foo(object,metaclass=MyType):


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

        print("Foo __init__")

    def __new__(cls, *args, **kwargs):
        print("Foo __new__",cls, *args, **kwargs)
        return object.__new__(cls)

f = Foo("Alex")
print("f",f)
print("fname",f.name)

自定义元类

特性

yaml是一个家喻户晓的 Python 工具,可以方便地序列化 / 逆序列化结构数据。YAMLObject 的任意子类支持序列化和反序列化(serialization & deserialization)。比如说下面这段代码:

class Monster(yaml.YAMLObject):
  yaml_tag = u'!Monster'
  def __init__(self, name, hp, ac, attacks):
    self.name = name
    self.hp = hp
    self.ac = ac
    self.attacks = attacks
  def __repr__(self):
    return "%s(name=%r, hp=%r, ac=%r, attacks=%r)" % (
       self.__class__.__name__, self.name, self.hp, self.ac,      
       self.attacks)

yaml.load("""
--- !Monster
name: Cave spider
hp: [2,6]    # 2d6
ac: 16
attacks: [BITE, HURT]
""")

Monster(name='Cave spider', hp=[2, 6], ac=16, attacks=['BITE', 'HURT'])

print yaml.dump(Monster(
    name='Cave lizard', hp=[3,6], ac=16, attacks=['BITE','HURT']))

# 输出
!Monster
ac: 16
attacks: [BITE, HURT]
hp: [3, 6]
name: Cave lizard

调用统一的 yaml.load(),就能把任意一个 yaml 序列载入成一个 Python Object;而调用统一的 yaml.dump(),就能把一个 YAMLObject 子类序列化。对于 load() 和 dump() 的使用者来说,他们完全不需要提前知道任何类型信息,这让超动态配置编程成了可能。在我的实战经验中,许多大型项目都需要应用这种超动态配置的理念。

比方说,在一个智能语音助手的大型项目中,我们有 1 万个语音对话场景,每一个场景都是不同团队开发的。作为智能语音助手的核心团队成员,我不可能去了解每个子场景的实现细节。

在动态配置实验不同场景时,经常是今天我要实验场景 A 和 B 的配置,明天实验 B 和 C 的配置,光配置文件就有几万行量级,工作量不可谓不小。而应用这样的动态配置理念,我就可以让引擎根据我的文本配置文件,动态加载所需要的 Python 类。

对于 YAML 的使用者,这一点也很方便,你只要简单地继承 yaml.YAMLObject,就能让你的 Python Object 具有序列化和逆序列化能力。是不是相比普通 Python 类,有一点“变态”,有一点“超越”?

metaclass底层实现

在 Python 的类型世界里,type 这个类就是造物的上帝

# Python 3 和 Python 2 类似
class MyClass:
  pass

instance = MyClass()

type(instance)
# 输出
<class '__main__.C'>

type(MyClass)
# 输出
<class 'type'>

instance 是 MyClass 的实例,而 MyClass 不过是“上帝”type 的实例

当我们定义一个类的语句结束时,真正发生的情况,是 Python 调用 type 的__call__, 运算符。简单来说,当你定义一个类时,写成下面这样时:

class MyClass:
  data = 1

Python 真正执行的是下面这段代码:

class = type(classname, superclasses, attributedict)

这里等号右边的type(classname, superclasses, attributedict) ,就是 type 的 __call__ 运算符重载,它会进一步调用:

type.__new__(typeclass, classname, superclasses, attributedict)
type.__init__(class, classname, superclasses, attributedict)

代码验证

class MyClass:
  data = 1

instance = MyClass()
MyClass, instance
# 输出
(__main__.MyClass, <__main__.MyClass instance at 0x7fe4f0b00ab8>)
instance.data
# 输出
1

MyClass = type('MyClass', (), {'data': 1})
instance = MyClass()
MyClass, instance
# 输出
(__main__.MyClass, <__main__.MyClass at 0x7fe4f0aea5d0>)

instance.data
# 输出
1

instance() 函数判断对象类型

使用instance()函数检测一个给定的对象是否属于(继承)某个类或类型,是为True,否为False

class MyClass:
    val1 = "String1"   #静态变量
    def __init__(self):
        self.val2 = "Value 2"

c = MyClass()
print(isinstance(c, MyClass))
l = [1, 2, 3, 4]
print(isinstance(l, list))
>>> 
True
True

函数重载

使子类必须重新写一遍 方法,来覆盖掉原有函数, 这里通过raise 一个exception来提示

class Entity():
      def __init__(self, object_type):
          print('parent class init called')
          self.object_type = object_type
      def get_context_length(self):
          raise Exception('get_context_length not implemented')
      def print_title(self):
          print(self.title)


class Document(Entity):
    def __init__(self, title, author, context):
        print('Document class init called')
        Entity.__init__(self, 'document')
        self.title = title
        self.author = author
        self.__context = context
    def get_context_length(self):
        return len(self.__context)

类拆分 (优化)

job 在其中出现了很多次,而且它们表达的是一个意义实体,这种情况下,我们可以考虑将这部分分解出来,作为单独的类。

class Person:
    def __init__(self, name, sex, age, job_title, job_description, company_name):
        self.name = name
        self.sex = sex
        self.age = age
        self.job_title = job_title
        self.job_description = description
        self.company_name = company_name
class Person:
    def __init__(self, name, sex, age, job_title, job_description, company_name):
        self.name = name
        self.sex = sex
        self.age = age
        self.job = Job(job_title, job_description, company_name)

class Job:
    def __init__(self, job_title, job_description, company_name):

        self.job_title = job_title
        self.job_description = description
        self.company_name = company_name