1)Python笔试基础知识

  1. 问以下类定义中哪些是类属性,哪些是实例属性?
    答:num 和 count 是类属性(静态变量),x 和 y 是实例属性;通常你应该考虑使用实例属性,而不是类属性(类属性通常仅用来跟踪与类相关的值)。

    1
    2
    3
    4
    5
    6
    class C:
    num = 0 #类属性
    def __init__(self):
    self.x = 4 #实例属性
    self.y = 5
    C.count = 6
  2. 如何优雅地避免访问对象不存在的属性(不产生异常)?

  • 第一种先使用 hasattr(object, name) 函数判断属性是否存在
  • 第二种方法是直接使用 getattr(object, name[, default]) 函数并设置 default 参数(返回对象指定的属性值,如果指定的属性不存在,返回default(可选参数)的值)


  1. 你真的理解了修饰符的用法吗?
    一个修饰符就是一个函数,它将被修饰的函数(再其下方)做为参数,并返回修饰后的同名函数或其它可调用的东西。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    @something
    def f():
    print("I love FishC.com!")

    # 相当于
    def f():
    print("I love FishC.com!")

    f = something(f)
  1. 鸭子类型(duck typing)
    鸭子类型是动态类型的一种风格,一个对象有效的语义不是由继承自特定的类或实现特定的接口,而是由当前方法和属性的集合决定;

在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的; 鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。

注意事项:

  • 静态类型语言转向动态类型语言的用户通常试图添加一些静态的(在运行之前的)类型检查
  • 从而影响了鸭子类型的益处和可伸缩性,并约束了语言的动态特性
  • DT类型应避免使用 type() 或 isinstance() 等测试类型是否合法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/python3
#鸭子类型案例了解

class Duck:
def quack(self):
print("Duck 类 呱呱呱!")
def feathers(self):
print("Duck 类 这个鸭子拥有灰白灰白的羽毛。")

class Person:
def quack(self):
print("Person 类 你才是鸭子你们全家人是鸭子!")
def feathers(self):
print("Person 类 这个人穿着一件鸭绒大衣。")

def in_the_forest(duck):
duck.quack()
duck.feathers()

#入口
def game():
'''Duck Typing 类型测试'''
donald = Duck()
john = Person()
in_the_forest(donald) #实际动态调用了Duck类定义的方法
in_the_forest(john)

print("###########",game.__doc__,"#################")
game()


#案例2:鸭子类型
def calc(a, b, c):
return (a + b) * c

>>> a = calc(1, 2, 3)
>>> b = calc([1, 2, 3], [4, 5, 6], 2)
>>> c = calc('love', 'FishC', 3)
>>> print(a)
9
>>> print(b)
[1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6]
>>> print(c)
loveFishCloveFishCloveFishC


########### Duck Typing 类型测试 #################
# Duck 类 呱呱呱!
# Duck 类 这个鸭子拥有灰白灰白的羽毛。
# Person 类 你才是鸭子你们全家人是鸭子!
# Person 类 这个人穿着一件鸭绒大衣。

总结:

  • 鸭子类型给予 Python 这样的动态语言以多态
  • 该方法即灵活,又提高了对程序员的要求
  • 多态的实现完全由程序员来约束强制实现(文档、清晰的代码和测试),并没有语言上的约束(如 C++ 继承和虚函数)


  1. Python 什么时候会调用到反运算的魔法方法?
    答:例如 a + b,如果 a 对象的 add 方法没有实现或者不支持相应的操作,那么 Python 就会自动调用 b 的 radd 方法。


  1. 如何使用静态方法、类方法或者抽象方法?(后续了解)
  • Python中方法的运作:方法是作为类的属性(attribute)存储的函数;
  • 静态方法: 是一类特殊的方法。有时我们需要写属于一个类的方法,但是不需要用到对象本身
  • 类方法:是绑定在类而非对象上的方法!它总会被绑定在其归属的类上,同时它第一个参数是类本身(记住:类同样是对象)
  • 抽象方法在一个基类中定义,但是可能不会有任何的实现。在 Java 中,这被描述为一个接口的方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
#!/usr/bin/python3
#[扩展阅读] 如何使用静态方法、类方法或者抽象方法

#1.Python 方法的运作
class Pizza(object):
def __init__(self, size):
self.size = size
def get_size(self):
return self.size

print(Pizza.get_size) #Python3中 归属于一个类的函数不再被看成未绑定方法(unbound method),但是作为一个简单的函数(返回函数地址)
#Python2中 类Pizza的属性get_size是一个非绑定的方法

print("Pizza类地址:",Pizza(12).get_size) #实例化对象与方法绑定,在 Python3 中bound原理是一样的,模型被简化了
print("get_size() 获取方法后值:",Pizza(12).get_size()) #实例化对象调用get-size方法

## 但是如何知道已绑定的方法被绑定在哪个对象上?技巧如下:
m = Pizza(42).get_size
print("Pizza对象",m.__self__,"\nPizza对象get_size方法地址即是bound method Pizza.get_size 类地址:",m == m.__self__.get_size)

############################## 执行结果 #######################################
# <function Pizza.get_size at 0x000002BF0D9C9598>
# Pizza类地址: <bound method Pizza.get_size of <__main__.Pizza object at 0x000002BF0D9F2978>>
# get_size() 获取方法后值: 12
# Pizza对象 <__main__.Pizza object at 0x000002BF0D9F2978>
# Pizza对象get_size方法地址即是bound method Pizza.get_size地址: <bound method Pizza.get_size of <__main__.Pizza object at 0x000002BF0D9F2978>>


#2.静态方法
# 静态方法避免了这样的情况:
# - 降低了阅读代码的难度:看到 @staticmethod 便知道这个方法不依赖与对象本身的状态;
# - 允许我们在子类中重载mix_ingredients方法。
# 如果我们使用在模块最顶层定义的函数 mix_ingredients,一个继承自 SPizza 的类若不重载 cook,可能不可以改变混合成份(mix_ingredients)的方式。

class SPizza(object):
@staticmethod #@ 修饰器
def mix_ingredients(x, y):
return x + y

#将方法 mix_ingredients 作为一个非静态的方法也可以 work,但是给它一个 self 的参数将没有任何作用。
def cook(self):
return self.mix_ingredients(self.cheese, self.vegetables)

print(SPizza().mix_ingredients(1,2)) # 3
print(SPizza.cook is SPizza().cook) # False 是因为前者是 类的方法 后者是实例化对象的方法嘛,

# >>> Pizza().mix_ingredients is Pizza().mix_ingredients
# True
# >>> Pizza().mix_ingredients is Pizza.mix_ingredients
# True

# >>> Pizza()
# <__main__.Pizza object at 0x10314b410>



#3.类方法
#类方法一般用于下面两种:
#- 工厂方法,被用来创建一个类的实例,完成一些预处理工作,如果我们使用一个 @staticmethod 静态方法,我们可能需要在函数中硬编码 Pizza 类的名称,使得任何继承自 Pizza 类的类不能使用我们的工厂用作自己的目的。
class Pizza(object):
def __init__(self, ingredients):
self.ingredients = ingredients

@classmethod #类方法
def from_fridge(cls, fridge):
return cls(fridge.get_cheese() + fridge.get_vegetables())

#- 静态方法调静态方法:如果你将一个静态方法分解为几个静态方法,你不需要硬编码类名但可以使用类方法
class Pizza(object):
def __init__(self, radius, height):
self.radius = radius
self.height = height

@staticmethod #静态方法
def compute_circumference(radius):
return math.pi * (radius ** 2)

@classmethod #类方法
def compute_volume(cls, height, radius):
return height * cls.compute_circumference(radius) #可以调用静态方法

def get_volume(self):
return self.compute_volume(self.height, self.radius)


#4.抽象方法
class Pizza(object):
def get_radius(self):
raise NotImplementedError #任何继承自 Pizza 的类将实现和重载 get_radius 方法,否则会出现异常。

# >>> Pizza()
# <__main__.Pizza object at 0x106f381d0>

# >>> Pizza().get_radius()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# File "<stdin>", line 3, in get_radius
# NotImplementedError

#有种提前引起错误发生的方法,那就是当对象被实例化时,使用 Python 提供的 abc 模块。
import abc
class BasePizza(object):
__metaclass__ = abc.ABCMeta

@abc.abstractmethod
def get_radius(self):
"""Method that should do something."""
# 使用 abc 和它的特类,一旦你试着实例化 BasePizza 或者其他继承自它的类,就会得到 TypeError:
# >>> BasePizza()
# Traceback (most recent call last):
# File "<stdin>", line 1, in <module>
# TypeError: Can't instantiate abstract class BasePizza with abstract methods get_radius

总结:

  1. Python 不需要对每个实例化的 Pizza 对象实例化一个绑定的方法。(绑定方法同样是对象,创建它们需要付出代价)
  2. 静态方法是代码一运行就会分配内存地址的 不管怎么调用 实例化类 都不会变
  3. 类方法是调用才会分配地址 随调用随分配


  1. yield 与 Generators(生成器)
    最近几年,生成器的功能变得越来越强大,它已经被加入到了 PEP;在协程(coroutine),协同式多任务处理(cooperative multitasking),以及异步 IO(asynchronous I/O)中广泛使用;

(1) 协程(协同程序)与子例程
调用一个普通的 Python 函数时,结束于 return 语句、异常或者函数结束(可以看作隐式的返回 None),函数中做的所有工作以及保存在局部变量中的数据都将丢失;

函数需要能够“保存自己的工作”,这时便是yield出现的最佳时期;

  • return 隐含的意思是函数正将执行代码的控制权返回给函数被调用的地方
  • yield 的隐含意思是控制权的转移是临时和自愿的,我们的函数将来还会收回控制权。

当生成器函数调用 yield,生成器函数的“状态”会被冻结,所有的变量的值会被保留下来,下一行要执行的代码的位置也会被记录,调用一次next()就指向下一个yield位置(永远不会退回指向)。
while 循环是用来确保生成器函数永远也不会执行到函数末尾的,只要调用 next() 这个生成器就会生成一个值(引出了一个处理无穷序列的常见方法(这类生成器也是很常见的));

当 yield 关键字返回 number 的值,而像 other = yield foo 这样的语句的意思是,“返回 foo 的值,这个值返回给调用者的同时将 other 的值也设置为那个值”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def get_primes(number):
while True:
if is_prime(number):
number = yield number #如普通函数return一样将值返回给调用则
number += 1

def print_successive_primes(iterations, base=10):
prime_generator = get_primes(base)
prime_generator.send(None) #必须调用设置None才能 通过 send 方法来将一个值“发送”给生成器。
for power in range(iterations):
print(prime_generator.send(base ** power)) #发生send值

# 首先我们打印的是 generator.send 的结果是OK的,因为 send 在发送数据给生成器的同时还返回生成器通过 yield 生成的值(就如同生成器中 yield 语句做的那样)。
# 看一下 prime_generator.send(None) 这一行,当你用 send 来“启动”一个生成器时(就是从生成器函数的第一行代码执行到第一个 yield 语句的位置),你必须发送 None。这
# 不难理解,根据刚才的描述,生成器还没有走到第一个 yield 语句,如果我们发生一个真实的值,这时是没有人去“接收”它的。一旦生成器启动了,我们就可以像上面那样发送数据了。

案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import random

def get_data():
"""返回0到9之间的3个随机数"""
return random.sample(range(10), 3)

def consume():
"""显示每次传入的整数列表的动态平均值"""
running_sum = 0
data_items_seen = 0

while True:
data = yield #生成器接收点 关键点
data_items_seen += len(data) # 每次调用值将会保留,下次执行的时候将会调用该值
running_sum += sum(data) #
print('The running average is {}'.format(running_sum / float(data_items_seen)))

def produce(consumer):
"""产生序列集合,传递给消费函数(consumer)"""
while True:
data = get_data()
print('Produced {}'.format(data))
consumer.send(data) #关键点 #通过 send 方法来将一个值“发送”给生成器。
yield #设置生成器

if __name__ == '__main__':
consumer = consume()
consumer.send(None) #启动生成器
producer = produce(consumer)

for _ in range(3):
print('Producing...')
next(producer)

########### 执行结果 ################
# 传递进去的值不会随着函数接收而消失,而是暂时进行了保存(以供下次使用);
# Producing...
# Produced [0, 9, 8]
# The running average is 5.666666666666667
# Producing...
# Produced [2, 3, 1]
# The running average is 3.8333333333333335
# Producing...
# Produced [3, 5, 2]
# The running average is 3.666666666666666

注意事项:

  • 协程就是生成器,正式的术语叫生成器而已;
  • 一个生成器函数的定义很像一个普通的函数,除了当它要生成一个值的时候,使用 yield 关键字而不是 return;
  • 生成器就是一类特殊的迭代器,所以生成器必须要定义一些方法(method),其中一个就是 next()
  • generator 是用来产生一系列值的,yield 则像是 generator 函数的返回结果
  • yield 唯一所做的另一件事就是保存一个 generator 函数的状态,generator 就是一个特殊类型的迭代器(iterator)
  • 和迭代器相似,我们可以通过使用 next() 来从 generator 中获取下一个值;通过隐式地调用 next() 来忽略一些值
  1. 属性访问问题魔法方法
    1
    2
    3
    4
    5
    6
    7
    8
    9
    def __setattr__(self, name, value):
    self.name = value + 1 #每当属性被赋值的时候, __setattr__() 会被调用,而里边的 self.name = value + 1 语句又会再次触发 __setattr__() 调用,导致无限递归。

    self.__dict__[name] = value + 1 #方法1
    super().__setattr__(name, value+1) #方法2


    >>> dir(super) #注意超类没有 __getattr__ 方法
    ['__class__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__gt__', '__hash__', '__init__', '__le__', '__lt__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__self__', '__self_class__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__thisclass__']
    WeiyiGeek.错误根源

    WeiyiGeek.错误根源

  1. 属性访问/描述符案例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    class Counter:
    def __init__(self):
    super().__setattr__('counter', 0)
    def __setattr__(self, name, value):
    super().__setattr__('counter', self.counter + 1) #关键点
    super().__setattr__(name, value)
    def __delattr__(self, name):
    super().__setattr__('counter', self.counter - 1) #关键点
    super().__delattr__(name)


    #值得学习
    class MyDes:
    def __init__(self, value = None):
    self.val = value
    def __get__(self, instance, owner):
    return self.val ** 2

    class Test(MyDes):
    x = MyDes(3)
    #注意不能下面这样进行调用
    def __init__(self):
    self.x = myDes(3)

    test = Test()
    print(test.x) # 9

    ######### 属性访问 ##############
    # >>> c = Counter()
    # >>> c.x = 1
    # >>> c.counter
    # 1
    # >>> c.y = 1
    # >>> c.z = 1
    # >>> c.counter
    # 3
    # >>> del c.x
    # >>> c.counter
    # 2
  1. Python 基于序列的三大容器类指的是什么吗?
    无疑是列表(List),元组(Tuple)和字符串(String)啦, 读 —— getitem(),写 —— setitem(),删除 —— delitem(),”


  1. 迭代相关知识点
  • 迭代就是重复反馈过程的活动,其目的是为了接近并达到所需的目标或结果;
  • 迭代不是一个容器,且判断一个容器是不是有迭代功能只需要查看iter() 和 next() 方法
  • 迭代器不能回退到上一个值,只能一往无前(),当容器中无元素时候,就能抛出 StopIteration 异常表示容器已经没有元素了
  • Py原生的数据结构中(但对于无法随机访问的数据结构 )set只能使用迭代器进行访问;
  1. 模块的知识点总结
  • 模块就是程序一个.py文件就是一个独立的模块
  • 如果你不想模块中的某个属性被 from…import * 导入,那么你可以给你不想导入的属性名称的前边加上一个下划线(_)
  • 模块容易错点,执行下边 a.py 或 b.py 任何一个文件,都会报错
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    # a.py
    from b import y
    def x():
    print('x')

    # b.py
    from a import x
    def y():
    print('y')

    >>> #这个是循环嵌套导入问题,都会抛出 ImportError 异常
    Traceback (most recent call last):
    File "/Users/FishC/Desktop/a.py", line 1, in <module>
    from b import x
    File "/Users/FishC/Desktop/b.py", line 1, in <module>
    import a
    File "/Users/FishC/Desktop/a.py", line 1, in <module>
    from b import x
    ImportError: cannot import name 'x'
    #这是因为在执行其中某一个文件(a.py)的加载过程中,会创建模块对象并执行对应的字节码。但当执行第一个语句的时候需要导入另一个文件(from b import y),因此 CPU 会转而去加载另一个文件(b.py)。同理,执行另一个文件的第一个语句(from a import x)恰好也是需要导入之前的文件(a.py)

    # 执行 b.py -> import a -> 查找 a 模块 -> 未发现 a 模块对象 -> 导入 a.py -> import b -> 查找 b 模块 -> 发现 b 模块对象 -> 接着往下执行字节码(import a 已执行过,Python 有机制确保不重复导入,因而不会再执行) -> a.x() -> 在 a 模块中找不到 x(),因为 a 还没有被完全导入嘛……

    #解决方法
    # a.py
    import b
    def x():
    print('x')

    if __name__ == "__main__":
    b.y()

    # b.py
    import a
    def y():
    print('y')

    if __name__ == "__main__":
    a.x()
  1. Python 是支持常量需要进行导入const?
    提示一:我们需要一个 Const 类
    提示二:重写 Const 类的某一个魔法方法,指定当实例对象的属性被修改时的行为
    提示三:检查该属性是否已存在
    提示四:检查该属性的名字是否为大写
    提示五:细心的鱼油可能发现了,怎么我们这个 const 模块导入之后就把它当对象来使用(const.NAME = “FishC”)了呢?

难道模块也可以是一个对象?
没错啦在 Python 中无处不对象,到处都是你的对象。使用以下方法可以将你的模块与类 A 的对象挂钩。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 该模块用于让 Python 支持常量操作
class Const:
def __setattr__(self, name, value):
if name in self.__dict__:
raise TypeError('常量无法改变!')

if not name.isupper():
raise TypeError('常量名必须由大写字母组成!')

self.__dict__[name] = value

'''
sys.modules 是一个字典,它包含了从 Python 开始运行起,被导入的所有模块。键就是模块名,值就是模块对象。
'''
import sys
sys.modules[__name__] = Const()


# 实际案例1:
# const 模块就是这道题要求我们自己写的
# const 模块用于让 Python 支持常量操作
import const

const.NAME = "FishC" #定义一个常量
print(const.NAME)

try:
# 尝试修改常量
const.NAME = "FishC.com"
except TypeError as Err:
print(Err)

try:
# 变量名需要大写
const.name = "FishC"
except TypeError as Err:
print(Err)


  1. 生成器知识点总结
  • 所谓的协同程序就是可以运行的独立函数调用,函数可以暂停或者挂起,并在需要的时候从程序离开的地方继续或者重新开始
  • 通过生成器来实现类似于协调程序的概念,生成器可以暂时挂起函数,并保留函数的局部变量等数据,然后在再次调用它的时候,从上次暂停的位置继续执行下去
  • 生成器所能实现的任何操作都可以由迭代器来代替,内部会创建iter和next方法;
  • 生成器的最大作用是可以“保留现场”,当下一次执行该函数是从上一次结束的地方开始,而不是重头再来。

案例:

1
2
3
4
5
6
7
8
9
10
#实现一个功能与 reversed() 相同(内置函数 reversed(seq) 是返回一个迭代器,是序列 seq 的逆序显示)的生成器
>>> for i in myRev("FishC"):
print(i, end='')

ChsiF
def myRev(data):
# 这里用 range 生成 data 的倒序索引
# 注意,range 的结束位置是不包含的
for index in range(len(data)-1, -1, -1):
yield data[index]

  1. name 属性含义总结
  • 所有模块都有一个 name 属性,name 的值取决于如何应用模块,在作为独立程序运行的时候 name 属性的值是 ‘main‘,而作为模块导入的时候,这个值就是该模块的名字了
  • 可以通过 sys 模块中的 path 变量显示出来(sys.path)
  • 可以将模块文件放在 site-packages 文件夹之后就能直接进行调用;
  • 分一个文件夹是普通文件夹还是包,看文件夹中是否有 init.py 文件
  • 必须在包文件夹中创建一个 init.py 的模块文件,内容可以为空。可以是一个空文件,也可以写一些初始化代码。

2)Python操作实验

  1. 多重继承的陷阱:支持多继承的面向对象编程都可能会导致钻石继承(菱形继承)问题
    WeiyiGeek.钻石继承

    WeiyiGeek.钻石继承

Q:钻石继承(菱形继承)会带来什么问题?
多重继承容易导致钻石继承(菱形继承)问题,上边代码实例化 D 类后我们发现 A 被前后进入了两次。

Q:这有什么危害?
我举个例子,假设 A 的初始化方法里有一个计数器,那这样 D 一实例化,A 的计数器就跑两次(如果遭遇多个钻石结构重叠还要更多),很明显是不符合程序设计的初衷的(程序应该可控,而不能受到继承关系影响)。

Q:如何避免钻石继承(菱形继承)问题?
为解决这个问题,Python 使用了一个叫“方法解析顺序(Method Resolution Order,MRO)”的东西,还用了一个叫 C3 的算法。
解释下 MRO 的顺序基本就是:在避免同一类被调用多次的前提下,使用广度优先和从左到右的原则去寻找需要的属性和方法。

在继承体系中,C3 算法确保同一个类只会被搜寻一次。
例子中如果一个属性或方法在 D 类中没有被找到,Python 就会搜寻 B 类,然后搜索 C类,如果都没有找到,会继续搜索 B 的基类 A,如果还是没有找到,则抛出“AttributeError”异常。
使用 类名.mro 获得 MRO 的顺序(注:object 是所有类的基类,金字塔的顶端):

1
2
>>> D.__mro__
(<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>)

解决方法:你应该召唤 super 函数大显神威

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class A():
def __init__(self):
print("进入A…")
print("离开A…")

class B(A):
def __init__(self):
print("进入B…")
super().__init__() #超类
print("离开B…")

class C(A):
def __init__(self):
print("进入C…")
super().__init__() #超类
print("离开C…")

class D(B, C):
def __init__(self):
print("进入D…")
super().__init__() #超类
print("离开D…")

############## 执行结果 ###############
# >>> d = D()
# 进入D…
# 进入B…
# 进入C…
# 进入A…
# 离开A…
# 离开C…
# 离开B…
# 离开D…

  1. 定义一个点(Point)类和直线(Line)类,使用 getLen 方法可以获得直线的长度。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    #设点 A(X1,Y1)、点 B(X2,Y2),则两点构成的直线长度 |AB| = √((x1-x2)^2+(y1-y2)^2)
    # Python 中计算开根号可使用 math 模块中的 sqrt 函数
    # 直线需有两点构成,因此初始化时需有两个点(Point)对象作为参数

    #!/usr/bin/python3
    #方法1:继承
    import math

    class Point:
    #初始化坐标
    def __init__(self,x1=0,y1=0,x2=0,y2=0):
    self.X1 = x1
    self.X2 = x2
    self.Y1 = y1
    self.Y2 = y2

    class Line(Point):
    msg = ''
    def __init__(self,msg,x1, y1, x2, y2):
    super().__init__(x1, y1, x2, y2)
    self.msg = msg
    print("坐标信息 (%d,%d), (%d,%d)" %(self.X1,self.Y1,self.X2,self.Y2))
    def getLen(self):
    print("当前Tips:%s,计算中....." %self.msg)
    return math.sqrt((self.X1 - self.X2) * (self.X1 - self.X2) + (self.Y1 - self.Y2) * (self.Y1 - self.Y2))

    zhixian = Line('计算两点之间距离',1,1,4,5)
    print(zhixian.getLen())

    #方法2:传入对象类似于继承(值得学习)
    class Point1():
    def __init__(self, x=0, y=0):
    self.x = x
    self.y = y

    def getX(self):
    return self.x

    def getY(self):
    return self.y

    class Line1():
    def __init__(self, p1, p2):
    self.x = p1.getX() - p2.getX()
    self.y = p1.getY() - p2.getY()
    self.len = math.sqrt(self.x*self.x + self.y*self.y)

    def getLen(self):
    return self.len

    ############## 执行结果 ###############
    # 坐标信息 (1,1), (4,5)
    # 当前Tips:计算两点之间距离,计算中.....
    # 5.0
  2. Python Mixin 编程机制学习说明?
    !important
    Mixin 编程是一种开发模式,是一种将多个类中的功能单元的进行组合的利用的方式,这听起来就像是有类的继承机制就可以实现,然而这与传统的类继承有所不同。
    通常 Mixin 并不作为任何类的基类,也不关心与什么类一起使用,而是在运行时动态的同其他零散的类一起组合使用。

Mixin 机制特点:

  • 可以在不修改任何源代码的情况下,对已有类进行扩展;
  • 可以保证组件的划分;
  • 可以根据需要,使用已有的功能进行组合,来实现“新”类;
  • 很好的避免了类继承的局限性,因为新的业务需要可能就需要创建新的子类。

Mixin 机制实现方法:

  • 多继承 (引用该类的模块都收到影响/不安全)
  • 插件方式

插件方式:通常我们希望修改对象的行为,而不是修改类的。同样的我们可以利用dict来扩展对象的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#!/usr/bin/python3
#功能:采用字典的方式来扩展对象方法 (值得学习)

class PlugIn(object): #注意这里的参数
def __init__(self):
self._exported_methods = []

def plugin(self, owner): #owner 即 我们combine 实例化的对象
for f in self._exported_methods:
print("类方法名 :"+f.__name__," 类方法地址:",f) #实际是AFeature/BFeature 方法名/方法地址地址
owner.__dict__[f.__name__] = f #在实例化对象c的字典之中加入将其他类方法

def plugout(self, owner):
for f in self._exported_methods:
del owner.__dict__[f.__name__]

#注意这里是扩展的Plugin类
class AFeature(PlugIn):
def __init__(self):
super(AFeature, self).__init__() #使用超类进行父类初始化
self._exported_methods.append(self.get_a_value) #初始化的时候讲本类的方法地址传入到父类的列表之中 (重点)

def get_a_value(self):
print("c可以被调用 a feature.")

#注意这里是扩展的Plugin类
class BFeature(PlugIn):
def __init__(self):
super(BFeature, self).__init__()
self._exported_methods.append(self.get_b_value)

def get_b_value(self):
print("c可以被调用 b feature.")

class Combine:
pass

c = Combine()
print("Plugin 中 owner 即是传入的 c 对象地址:",c)
AFeature().plugin(c) #作为插件 将A类中方法通过插件 加入到 c 对象中
BFeature().plugin(c) #同上
c.get_a_value()
c.get_b_value()

#######################执行结果###########################
# >python demo3.6.py
# Plugin 中 owner 即是传入的 c 对象地址: <__main__.Combine object at 0x000001D024DBDC18>
# 类方法名 :get_a_value 类方法地址: <bound method AFeature.get_a_value of <__main__.AFeature object at 0x000001D024DBDCC0>>
# 类方法名 :get_b_value 类方法地址: <bound method BFeature.get_b_value of <__main__.BFeature object at 0x000001D024DBDC88>>
# c可以被调用 a feature.
# c可以被调用 b feature.

  1. 类魔术方法实现定时器以及定时器相加,Python 具备一个叫做 timeit 的完美计时工具;
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    #!/usr/bin/python3
    #功能:采用魔术方法实现定时器
    import time as t

    class MyTimer:
    def __init__(self):
    self.prompt = "未开始计时!"
    self.lasted = 0.0
    self.begin = 0
    self.end = 0
    self.default_timer = t.perf_counter

    def __str__(self):
    return self.prompt

    __repr__ = __str__

    def __add__(self, other):
    result = self.lasted + other.lasted
    prompt = "总共运行了 %0.2f 秒" % result
    return prompt

    # 开始计时
    def start(self):
    self.begin = self.default_timer()
    self.prompt = "提示:请先调用 stop() 停止计时!"
    print("计时开始...")

    # 停止计时
    def stop(self):
    if not self.begin:
    print("提示:请先调用 start() 进行计时!")
    else:
    self.end = self.default_timer()
    self._calc()
    print("计时结束!")

    # 内部方法,计算运行时间
    def _calc(self):
    self.lasted = self.end - self.begin
    self.prompt = "总共运行了 %0.2f 秒" % self.lasted

    # 为下一轮计时初始化变量
    self.begin = 0
    self.end = 0

    # 设置计时器(time.perf_counter() 或 time.process_time())
    def set_timer(self, timer):
    if timer == 'process_time':
    self.default_timer = t.process_time
    elif timer == 'perf_counter':
    self.default_timer = t.perf_counter
    else:
    print("输入无效,请输入 perf_counter 或 process_time")

    a = MyTimer()
    b = MyTimer()

    a.Start()
    b.Start()

    t.sleep(5)

    a.Stop()
    b.Stop()
    print("a 对象", a," b 对象" ,b)
    print(a + b)


    # 实现程序运行计时 (实例化的时候传入 func)
    class FuncTimer:
    def __init__(self, func, number=1000000):
    self.prompt = "未开始计时!"
    self.lasted = 0.0
    self.default_timer = t.perf_counter
    self.func = func
    self.number = number

    def __str__(self):
    return self.prompt

    __repr__ = __str__

    def __add__(self, other):
    result = self.lasted + other.lasted
    prompt = "总共运行了 %0.2f 秒" % result
    return prompt

    # 内部方法,计算运行时间
    def timing(self):
    self.begin = self.default_timer()
    for i in range(self.number):
    self.func()
    self.end = self.default_timer()
    self.lasted = self.end - self.begin
    self.prompt = "总共运行了 %0.2f 秒" % self.lasted

    # 设置计时器(time.perf_counter() 或 time.process_time())
    def set_timer(self, timer):
    if timer == 'process_time':
    self.default_timer = t.process_time
    elif timer == 'perf_counter':
    self.default_timer = t.perf_counter
    else:
    print("输入无效,请输入 perf_counter 或 process_time")

    ######## 执行结果 #############
    # 计时开始...
    # 计时开始...
    # 计时停止!
    # 计时停止!
    # a 对象 总共运行了5秒 b 对象 总共运行了5秒
    # 总共运行了10秒
  1. 采用类属性访问方式进行设置描述符,实现华氏度与摄氏度之间的转换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    #!/usr/bin/python3
    #温湿度类实现:摄氏度,华氏度

    #摄氏度类
    class Celsius:
    #初始化构造方法
    def __init__(self,value = 26.0):
    self.value = float(value)

    def __get__(self,instance,owner):
    return self.value

    def __set__(self,instance,value):
    self.value =float(value)

    #华氏度类
    class Fahrenheit:
    def __get__(self,instance,owner):
    return instance.cel * 1.8 + 32 #返回华氏度 关键点

    def __set__(self,instance,value):
    instance.cel = (float(value) - 32) / 1.8 #返回摄氏度 关键点

    #温度类
    class Tempareture:
    cel = Celsius() #摄氏度类
    fah = Fahrenheit() #华氏度类


    temp = Tempareture()
    temp.cel = 37.5 #进行赋值的时候 cel 对象 value 属性 = 37.5
    print("摄氏度 :%.2f , 华氏度 :%.2f" %(temp.cel,temp.fah)) #temp.fah 进行请求触发 华氏度类 __get__ => 返回华氏度的计算后的值
    temp.fah = 75.0
    print("摄氏度 :%.2f , 华氏度 :%.2f" %(temp.cel,temp.fah))


    ############ 执行结果 #############
    # 摄氏度 :37.50 , 华氏度 :99.50
    # 摄氏度 :23.89 , 华氏度 :75.00


  2. 魔法方法的利用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    #1. 只需要重载 __lshift__ 和 __rshift__ 魔法方法即可
    class Nstr(str):
    def __lshift__(self, other):
    return self[other:] + self[:other] #值得学习 LED 液晶显示平

    def __rshift__(self, other):
    return self[-other:] + self[:-other] #

    ############### 执行结果 #############
    # >>> a = Nstr('I love FishC.com!')
    # >>> a << 3
    # 'ove FishC.com!I l'
    # >>> a >> 3
    # 'om!I love FishC.c'


    #2. 定义一个类 Nstr,当该类的实例对象间发生的加、减、乘、除运算时,将该对象的所有字符串的 ASCII 码之和进行计算:
    # 注意我们必须要用到 __new__ 方法,因为 str 是不可变类型
    # 所以我们必须在创建的时候将它初始化
    class Nstr(int):
    def __new__(cls, arg=0):
    if isinstance(arg, str):
    total = 0
    for each in arg:
    total += ord(each) #转换成ascii码
    arg = total
    return int.__new__(cls, arg) #返回实例初始化处理后的结果

    ############### 执行结果 #############
    # >>> a = Nstr('FishC')
    # >>> b = Nstr('love')
    # >>> a + b
    # 899
    # >>> a - b
    # 23
    # >>> a * b
    # 201918
    # >>> a / b
    # 1.052511415525114
    # >>> a // b
    # 1
  3. 根据课堂上的例子,定制一个列表,同样要求记录列表中每个元素被访问的次数。这一次我们希望定制的列表功能更加全面一些,比如支持 append()、pop()、extend() 原生列表所拥有的方法。你应该如何修改呢?

要求1:实现获取、设置和删除一个元素的行为(删除一个元素的时候对应的计数器也会被删除)
要求2:增加 counter(index) 方法,返回 index 参数所指定的元素记录的访问次数
要求3:实现 append()、pop()、remove()、insert()、clear() 和 reverse() 方法(重写这些方法的时候注意考虑计数器的对应改变).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
class CountList(list):
def __init__(self, *args):
super().__init__(args)
self.count = []
for i in args:
self.count.append(0)

def __len__(self):
return len(self.count)

def __getitem__(self, key):
self.count[key] += 1
return super().__getitem__(key)

def __setitem__(self, key, value):
self.count[key] += 1
super().__setitem__(key, value)

def __delitem__(self, key):
del self.count[key]
super().__delitem__(key)

def counter(self, key):
return self.count[key]

def append(self, value):
self.count.append(0)
super().append(value)

def pop(self, key=-1):
del self.count[key]
return super().pop(key)

def remove(self, value):
key = super().index(value)
del self.count[key]
super().remove(value)

def insert(self, key, value):
self.count.insert(key, 0)
super().insert(key, value)

def clear(self):
self.count.clear()
super().clear()

def reverse(self):
self.count.reverse()
super().reverse()