每日分享
Somewhere, something incredible is waiting to be known.
总有令人惊叹的东西,在某处等着被发现。
1.3编程范式
上篇文章传送门『我是个链接』
上篇文章对 Python 的一些数据结构和常用算法做了归纳概括,很多东西还需大家多多练习才能掌握,算法需要理解,而非记忆。
本篇文章将开始编程范式的相关内容,开始咯~
1.3.1面向对象基础以及 Python 类
1.3.1.1什么是面向对象编程
Object Oriented Programming(OOP)
1.把对象作为基本单元,把对象抽象成类(Class),包含成员和方法。
2.三个特点:数据封装、继承和多态。
多态:同一操作作用于不同的对象,可以有不同的解释,产生不同的执行结果
3.Python 中使用类来实现面向对象的编程。之前我们是过程式编程以函数为基本单位。
1.3.1.2 Python 中如何创建类
我们简单的定义一个类:
class Person(object): def __init__(self, name, age): self.name = name self.age = age def print_name(self): print(‘my name is {}‘.format(self.name))
Person 这个类在初始化的时候,包含名字和年龄两个属性。同时类具有一个方法,可以输出姓名。
注意:在类中属性前加单下划线表示私有属性,不希望外界进行访问。在类中两个下划线表示的是魔法方法。
1.3.1.3组合和继承
优先使用组合而非继承
1.组合是使用其他的类实例作为自己的一个属性(Has a 关系)
2.子类继承父类的属性和方法(Is a 关系)
3.优先使用组合保持代码简单
1.3.1.4类变量和实例变量
区分类变量和实例变量
1.类变量由所有实例共享
2.实例变量由实例单独享有,不同实例之间不影响
3.当我们需要在一个类的不同实例之间共享变量的时候,需要使用类变量。
下面我们举一个简单的例子:
class Person: Country = ‘China‘ # 类变量 def __init__(self, name): self.name = name # 实例变量 def print_name(self): print(self.name)
上述例子中,我们可以知道,每一个实例都拥有 Country 这个属性,而他们的 name 则是自己独有的,互不相同,取决于构造实例时的输入。
1.3.1.5 classmethod/staticmethod 区别
它们经常被用作类方法的装饰器。
1.都可以通过 Class.method() 的方式使用
2.classmethod 第一个参数是 cls,可以引用类变量。使用此装饰器装饰后,表明是类方法,可以通过实例对象和类对象去访问;类方法还有一个用途就是可以对类属性进行修改。
3.staticmethod 使用起来和普通函数一样,只不过放在类里面去组织。使用此装饰器装饰后,表明是静态方法,静态方法不需要多定义参数,可以通过对象和类来访问。
小总结:
1.从类方法和实例方法以及静态方法的定义形式就可以看出来,类方法的第一个参数是类对象cls,那么通过cls引用的必定是类对象的属性和方法; 2.实例方法的第一个参数是实例对象self,那么通过self引用的可能是类属性、也有可能是实例属性(这个需要具体分析),不过在存在相同名称的类属性和实例属性的情况下,实例属性优先级更高。 3.静态方法中不需要额外定义参数,因此在静态方法中引用类属性的话,必须通过类实例对象来引用
1.3.1.6什么是元类?使用场景?
元类(Meta Class)是创建类的类
1.元类允许我们控制类的生成,比如修改类的属性等等。
2.使用 type 来定义元类。
3.元类最常见的一个使用场景就是 ORM 框架。
我们使用例子说明一下:
class Base: pass class Child: pass # 等价定义 注意 Base 后要加上逗号否则就不是 tuple 了 SameChild = type(‘Child‘, (Base,), {}) # 加上方法 class ChildWithMethod(Base): bar = True def hello(self): print(‘hello‘) def hello(self): print(‘hello‘) # 等价定义 ChildWithMethod = type( ‘ChildWithMethod‘, (Base,), {‘bar‘: True, ‘hello‘: hello} ) # 元类继承自 type class LowercaseMeta(type): """修改类的属性名称为小写的元类""" def __new__(mcs, name, bases, attrs): lower_attrs = {} for k, v in attrs.items(): # 排除magic method if not k.startswith(‘__‘): lower_attrs[k.lower()] = v else: lower_attrs[k] = v return type.__new__(mcs, name, bases, lower_attrs) class LowercaseClass(metaclass=LowercaseMeta): BAR = True def HELLO(self): print(‘hello‘) # 你会发现“BAR”和“HELLO”都变成了小写 print(dir(LowercaseClass)) # 用一个类的实例调用hello方法,我们修改了类定义时候的属性名! LowercaseClass().hello()
类中
__new__
用来生成实例,__init__
初始化实例
1.3.2 Python 装饰器
如果想给一个类扩充功能,可以使用组合和继承。装饰器可以在不修改原函数的基础上增添一些新的功能。
1.3.2.1什么是装饰器
Decorator
1.Python 中一切皆对象,函数也可以当做参数传递
2.装饰器是接受函数作为参数,添加功能后返回一个新函数的函数(类)
3.Python 中通过 @ 使用装饰器
1.3.2.2编写一个记录函数耗时的装饰器
import time
# 接受一个函数作为参数
def log_time(func): def _log(*args, **kwargs): beg = time.time() res = func(*args, **kwargs) print(‘use time:{}‘.format(time.time()-beg)) return res return _log @log_time def mysleep(): time.sleep(1) mysleep()
@ 代表的是装饰器语法糖
我们是用装饰器等价于下面的形式:
newsleep = log_time(mysleep) newsleep()
装饰器本质上只是一个函数,只是它特殊在将函数作为参数进行接收,调用完原始函数之后返回一个新的函数。
1.3.2.3如何使用类编写装饰器
import time
class LogTime: def __call__(self, func): def _log(*args, **kwargs): beg = time.time() res = func(*args, **kwargs) print(‘use time: {}‘.format(time.time()-beg)) return res return _log @LogTime() def mysleep(): time.sleep(1) mysleep()
1.3.2.4如何给装饰器增加参数
我们可以使用类装饰器比较方便实现装饰器参数
import time
class LogTime: def __init__(self, use_int=False): self.use_int = use_int def __call__(self, func): def _log(*args, **kwargs): beg = time.time() res = func(*args, **kwargs) if self.use_int: print(‘use time: {}‘.format( int(time.time()-beg)) ) else: print(‘use time: {}‘.format( time.time() - beg) ) return res return _log @LogTime(True) def mysleep(): time.sleep(1) mysleep()
1.3.3设计模式之创建型模式 Python 应用
在动态语言里面,设计模式关注的会偏少一点。不像在 Java 里面,会涉及到大量的设计模式。比如三种设计模式:创建型、结构型和行为型。之所以动态语言比如 Python 里面设计模式讲的少,并不是说它不重要,而是动态语言自己已经实现了一些设计模式,比如迭代器模式,装饰器模式 Python 本身就支持。
1.3.3.1常见创建型设计模式
工厂模式(Factory):解决对象创建问题。
构造模式(Builder):控制复杂对象的创建
原型模式(Prototype):通过原型的克隆创建新的实例
单例(Borg/Singleton):一个类只能创建同一个对象
对象池模式(Pool):预先分配同一类型的一组实例
惰性计算模式(Lazy Evaluation):延迟计算(Python 的 property)
1.3.3.2工厂模式
我们先来简单的看一下工厂模式:
1.解决对象的创建问题
2.解耦对象的创建和使用
3.包括工厂方法和抽象工厂
下面我们先来展示一个工厂方法的例子:
class DogToy: def speak(self): print("wang wang~~") class CatToy: def speak(self): print("miao miao~~") def toy_factory(toy_type): if toy_type == ‘dog‘: return DogToy() elif toy_type == ‘cat‘: return CatToy()
学习设计模式的一个有效的方式就是自己尝试写个示例代码来演示它。
1.3.3.3构造模式
下面我们再来简单的理解一下什么是构造模式(Builder):
1.用来控制复杂对象的构造
2.创建和表示分离。比如你要买电脑,工厂模式直接给你需要的电脑。但是构造模式允许你自己定义电脑的配置,组装完成后给你。
下面我们用一个书上的例子来解释一下:
class Computer: def __init__(self, serial_number): self.serial = serial_number self.memory = None self.hdd = None self.gpu = None def __str__(self): info = (‘Memory: {}GB‘.format(self.memory), ‘Hard Disk: {}GB‘.format(self.hdd), ‘Graphics Card: {}‘.format(self.gpu)) return ‘\n‘.join(info) class ComputerBuilder: def __init__(self): self.computer = Computer(‘AG23385193‘) def configure_memory(self, amount): self.computer.memory = amount def configure_hdd(self, amount): self.computer.hdd = amount def configure_gpu(self, gpu_model): self.computer.gpu = gpu_model class HardwareEngineer: def __init__(self): self.builder = None def construct_computer(self, memory, hdd, gpu): self.builder = ComputerBuilder() [step for step in (self.builder.configure_memory(memory), self.builder.configure_hdd(hdd), self.builder.configure_gpu(gpu))] @property def computer(self): return self.builder.computer # 使用Builder,可以创建多个Builder类实现不同的组装方式。 engineer = HardwareEngineer() engineer.construct_computer(hdd=2048, memory=16, gpu=‘GeForce GTX 2080 Ti‘) computer = engineer.computer print(computer)
1.3.3.4原型模式
这种模式平时接触的会比较少,此处不再用代码进行演示,只简单的进行说明即可。
1.通过克隆原型来创建新的实例
2.可以使用相同的原型,通过修改部分属性来创建新的示例
3.用途:对于一些创建实例开销比较高的地方可以用原型模式
1.3.3.5单例模式
单例模式是我们经常碰到的一种模式,在面试的过程中遇到的也会很多,大部分都会要求手写代码。所以此处要格外的进行注意。
单例模式就是一个类创建出来的对象都是同一个。其实我们经常接触它,也许你不注意而已,比如 Python 的模块其实就是单例的,只会导入一次。不论你在代码里面 import 多少次,最后其实解释器只导入一次。
下面我们使用共享同一个实例的方式来创建单例模式。
class Singleton: def __new__(cls, *args, **kwargs): if not hasattr(cls, ‘_instance‘): _instance = super().__new__(cls, *args, **kwargs) cls._instance = _instance return cls._instance class MyClass(Singleton): pass c1 = MyClass() c2 = MyClass() assert c1 is c2
1.3.4设计模式之结构型模式 Python 应用
1.3.4.1常见结构型设计模式
装饰器模式(Decorator):无需子类化扩展对象功能
代理模式(Proxy):把一个对象的操作代理到另一个对象
适配器模式(Adapter):通过一个间接层适配统一接口
外观模式(Facade):简化复杂对象的访问问题
享元模式(Flyweight):通过对象复用(池)改善资源利用,比如连接池
Model-View-Controller(MVC):解耦展示逻辑和业务逻辑
1.3.4.2代理模式
什么是代理模式(Proxy)呢?
1.把一个对象的操作代理到另一个对象
2.这里又要提到我们之前实现的 Stack/Queue ,把操作代理到 deque
3.通常使用 has-a 组合关系
之前我们实现的栈就用到了代理模式。还有一个常用的地方就是实现一些安全相关的东西。比如说我们有一个比较裸的接口,但是我们想去安全的访问它,我们可以加一个代理间接层,在代理层做一些校验的工作。
1.3.4.3适配器模式
什么是适配器模式(Adapter):
1.把不同对象的接口适配到同一个接口
2.想象一个多功能充电头,可以给不同的电器充电,充当了适配器
3.当我们需要给不同的对象统一接口的时候可以使用适配器模式
class Dog(object): def __init__(self): self.name = "Dog" def bark(self): return "woof!" class Cat(object): def __init__(self): self.name = "Cat" def meow(self): return "meow!" class Adapter: def __init__(self, obj, **adapted_methods): """We set the adapted methods in the object‘s dict""" self.obj = obj self.__dict__.update(adapted_methods) def __getattr__(self, attr): """All non-adapted calls are passed to the object""" return getattr(self.obj, attr) objects = [] dog = Dog() objects.append(Adapter(dog, make_noise=dog.bark)) cat = Cat() objects.append(Adapter(cat, make_noise=cat.meow)) for obj in objects: print("A {0} goes {1}".format(obj.name, obj.make_noise()))
1.3.5设计模式之行为型模式 Python 应用
1.3.5.1.常见学习行为型设计模式
迭代器模式(Iterator):通过统一的接口迭代对象
观察者模式(Observer):对象发生改变的时候,观察者执行相应动作
策略模式(Strategy):针对不同规模输入使用不同的策略
1.3.5.2迭代器模式
1.Python 内置对迭代器模式的支持
2.比如我们可以用 for 遍历各种 Iterable 的数据类型
3.Python 里可以实现 __next__
和 __iter__
实现迭代器
可迭代的对象只需实现
__iter__
方法即可
from collections import deque class Stack(object): def __init__(self): self._deque = deque() def push(self, value): return self._deque.append(value) def pop(self): return self._deque.pop() def empty(self): return len(self._deque) == 0 def __iter__(self): res = [] for i in self._deque: res.append(i) for i in reversed(res): yield i
1.3.5.3观察者模式
1.发布订阅是一种最常用的实现方式
订阅者在每次发布者发布消息的时候就会收到通知,执行相应的动作。
2.发布订阅用于解耦逻辑
3.可以通过回调等方式实现,当发生事件时,调用相应的回调函数
我们下面用一个简单的 demo 演示:
class Publisher: # 发布者 def __init__(self): self.observers = [] # 观察者 def add(self, observer): # 加入观察者 if observer not in self.observers: self.observers.append(observer) else: print(‘Failed to add: {}‘.format(observer)) def remove(self, observer): # 移除观察者 try: self.observers.remove(observer) except ValueError: print(‘Failed to remove: {}‘.format(observer)) def notify(self): # 调用观察者的回调 [o.notify_by(self) for o in self.observers] class Formatter(Publisher): def __init__(self, name): super().__init__() self.name = name self._data = 0 @property def data(self): return self._data @data.setter def data(self, new_value): self._data = int(new_value) # data在被合法赋值以后会执行notify self.notify() class BinaryFormatter: """订阅者""" def notify_by(self, publisher): print("{}: ‘{}‘ has now bin data = {}".format( type(self).__name__, publisher.name, bin(publisher.data) )) # 发布者 df = Formatter(‘formatter‘) # 订阅者 bf = BinaryFormatter() df.add(bf) # 设置的时候调用订阅者的notify_by df.data = 3
1.3.5.4策略模式
策略模式(Strategy)是根据不同的输入采用不同的策略,比如买东西超过 10 个 8 折,超过 20 个打 7 折。对外暴露统一的接口,内部采用不同的策略计算。
就上面提到的例子我们用代码作以实现:
class Order: def __init__(self, price, discount_strategy=None): self.price = price self.discount_strategy = discount_strategy def price_after_discount(self): if self.discount_strategy: discount = self.discount_strategy(self) else: discount = 0 return self.price - discount def __repr__(self): fmt = "<Price: {}, price after discount: {}>" return fmt.format( self.price, self.price_after_discount() ) def ten_percent_discount(order): return order.price * 0.10 def on_sale_discount(order): return order.price * 0.25 + 20 def main(): order0 = Order(100) order1 = Order(100, discount_strategy=ten_percent_discount) order2 = Order(100, discount_strategy=on_sale_discount) print(order0) print(order1) print(order2) main()
1.3.6 Python 函数式编程
Python 支持部分函数式编程特性
1.把电脑的运算视作数学上的函数计算(lambda 演算)
不要问是什么,一般人是涉及不到的,这是学术上的内容
2.高阶函数: map/reduce/filter
3.无副作用,相同的参数调用始终产生同样的结果
map函数的使用
In [1]: map(lambda x:x*2, range(10)) Out[1]: <map at 0x1c4ae642f98> In [2]: list(map(lambda x:x*2, range(10))) Out[2]: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
但是我们一般推荐列表推到代替 map
In [3]: [ i*2 for i in range(10)] Out[3]: [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
reduce函数的使用
In [5]: reduce(lambda x,y: x+y, range(1, 6)) Out[5]: 15 In [6]: 1+2+3+4+5 Out[6]: 15
filter函数的使用
In [7]: list(filter(lambda x: x%2==0, range(10))) Out[7]: [0, 2, 4, 6, 8] In [8]: [i for i in range(10) if i % 2 == 0] Out[8]: [0, 2, 4, 6, 8]
1.3.6.1闭包(Closure)
1.绑定了外部作用域的变量的函数
2.即使程序离开外部作用域,如果闭包仍然可见,绑定变量不会销毁。
3.每次运行外部函数都会重新创建闭包
闭包:引用了外部自由变量的函数 自由变量:不在当前函数定义的变量 特性:自由变量会和闭包函数同时存在
下面简单的概括一下就是:在一个函数中定义了一个另外一个函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用,这样就构成了一个闭包。
from functools import wraps def cache(func): store = {} @wraps(func) def _(n): if n in store: return store[n] else: res = func(n) store[n] = res return res return _ @cache def f(n): if n <= 1: return 1 return f(n-1) + f(n-2) print(f(10))