面向对象程序设计


In [7]:
class Circle(object):
    PI = 3.14 #类变量
    def __init__(self,radius):
        self.radius = radius #实例变量
    def get_areas(self):
        return PI * self.radius * self.radius

mycircle = Circle(2) #实例化
print(mycircle.radius) # 实例变量
print(mycircle.PI) #类变量
print(Circle.PI) #也可以使用类名直接调用类变量


2
3.14
3.14

当访问mycircle.radius,实际上是通过字典 mycircle.dict 查看相应的值。但是,如果回到类这一层,属性也是存储在类的字典里面:


In [8]:
Circle.__dict__


Out[8]:
mappingproxy({'PI': 3.14,
              '__dict__': <attribute '__dict__' of 'Circle' objects>,
              '__doc__': None,
              '__init__': <function __main__.Circle.__init__>,
              '__module__': '__main__',
              '__weakref__': <attribute '__weakref__' of 'Circle' objects>,
              'get_areas': <function __main__.Circle.get_areas>})

In [9]:
# 如果加上继承呢?
class Widget(object):
    copyright = 'witrett, inc.'

class Circle(Widget):
    PI = 3.14
    # copyright = 'circle copyright'
    def __init__(self, radius):
        self.radius = radius

mycircle = Circle(2)
print(type(mycircle).mro()) 
# mro即method resolution order,主要用于在多继承时判断调的属性的路径(来自于哪个类)。后续会进一步介绍
print(mycircle.copyright) #显然,这里是使用Widget中的变量copyright


[<class '__main__.Circle'>, <class '__main__.Widget'>, <class 'object'>]
witrett, inc.

In [10]:
Circle.__dict__


Out[10]:
mappingproxy({'PI': 3.14,
              '__doc__': None,
              '__init__': <function __main__.Circle.__init__>,
              '__module__': '__main__'})

也就是说,在查找属性或者方法的时候,会递归的查找mro中的内容,直到找到一个匹配项。

在传统的Python编码中,我们可以用下面的方式破坏面向对象的封装性:


In [11]:
class Widget(object):
  copyright = 'witrett, inc.'

class Circle(Widget):
  PI = 3.14
  def __init__(self, radius):
    self.radius = radius
    self.circumference = 2 * self.radius * self.PI

mycircle = Circle(2)
mycircle.radius = 3
mycircle.circumference #呵呵,修改并没生效


Out[11]:
12.56

可以看到,虽然尝试使用mycircle.radius = 3这个语句尝试改变实例属性,但是并没有成功


In [12]:
# 尝试一下如何改变这一切:

class Circle(Widget):
  PI = 3.14
  def __init__(self, radius):
    self.radius = radius

  @property
  def circumference(self):
    return 2 * self.radius * self.PI #现在ok

mycircle = Circle(2)
mycircle.radius = 3
mycircle.circumference #呵呵,属性修改成功


Out[12]:
18.84

于是,当我们按照这样的方式obj.foo在对象上访问一个属性时,就可以得到如下所示的一些简单的规则:

  1. 如果有该命名的属性,则直接访问该属性的值obj.foo
  2. 访问obj.__dict__
  3. 或者查询 type(obj).__dict__
  4. 直到在mro中找到合适的匹配项
  5. 赋值操作总是对于obj.__dict__ 创建一个键值对
  6. 直到设定一个setter属性

MRO 和C3 算法

MRO,主要用于在多继承时判断所调用属性的路径。

本地优先级:指声明时父类的顺序,比如C(A,B),如果访问C类对象属性时,应该根据声明顺序,优先查找A类,然后再查找B类。 单调性:如果在C的解析顺序中,A排在B的前面,那么在C的所有子类里,也必须满足这个顺序。


In [22]:
def c3_lineration(kls):
    if len(kls.__bases__) == 1:
        return [kls, kls.__base__]
    else:
        l = [c3_lineration(base) for base in kls.__bases__]
        l.append([base for base in kls.__bases__])
        return [kls] + merge(l)
    
def merge(args):
    if args:
        for mro_list in args:
            for class_type in mro_list:
                for comp_list in args:
                    if class_type in comp_list[1:]:
                        break
                else:
                    next_merge_list = []
                    for arg in args:
                        if class_type in arg:
                            arg.remove(class_type)
                            if arg:
                                next_merge_list.append(arg)
                        else:
                            next_merge_list.append(arg)
                    return [class_type] + merge(next_merge_list)
        else:
            raise Exception
    else:
        return []

                    

class A(object):pass
class B(object):pass
class C(object):pass
class E(A,B):pass
class F(B,C):pass
class G(E,F):pass

print(c3_lineration(G))


[<class '__main__.G'>, <class '__main__.E'>, <class '__main__.A'>, <class '__main__.F'>, <class '__main__.B'>, <class '__main__.C'>, <class 'object'>]

其实只需用知道以下的规则即可:

mro(G) = [G] + merge(mro[E], mro[F], [E,F]) = [G] + merge([E,A,B,O], [F,B,C,O], [E,F]) = [G,E] + merge([A,B,O], [F,B,C,O], [F]) = [G,E,A] + merge([B,O], [F,B,C,O], [F]) = [G,E,A,F] + merge([B,O], [B,C,O]) = [G,E,A,F,B] + merge([O], [C,O]) = [G,E,A,F,B,C] + merge([O], [O]) = [G,E,A,F,B,C,O]

委托(delegation)


In [ ]:
# 这个例子尝试给他小伙伴们解释getattr和__getattr__的使用:虽然类wrapper没有定义append方法,但是可以通过getattr调用list的append方法。

In [13]:
class wrapper:
    def __init__(self, object):
        self.wrapped = object
    
    def __getattr__(self, attrname):
        print('Trace:', attrname)
        return getattr(self.wrapped, attrname)

x = wrapper([1,2,3])
x.append(4)


Trace: append

In [5]:
x


Trace: _ipython_canary_method_should_not_exist_
Trace: _ipython_display_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_html_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_markdown_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_svg_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_png_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_pdf_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_jpeg_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_latex_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_json_
Trace: _ipython_canary_method_should_not_exist_
Trace: _repr_javascript_
Out[5]:
<__main__.wrapper at 0x110116390>

In [14]:
x.wrapped


Out[14]:
[1, 2, 3, 4]

这个示例有两个类,cook和material:


In [15]:
class cook:
    def __init__(self, material='rice'):
        self.material = material
    
    def boil(self):
        print('had boiled ', self.material)


class material:
    def __init__(self, rice):
        self.rice = rice
        self.meth = cook()
    
    def clean(self):
        print('clean up first')

    def __getattr__(self, attr):#通过这样的方式,一旦material的实例属性找不到需要的实例变量活着实例方法,就从cook类中寻找
        return getattr(self.meth, attr)
    
m = material('rice')
m.boil()# 注意此时m是material类的实例,但是boil方法属于cook类,就是通过上面的getattr语句连接起来


had boiled  rice

原本material和cook类之间的关系仅仅只是在material类的‘构造’函数中,实例化了cook类,一旦在material的实例中没有找到需要的方法,就自动加载cook类中的方法。


In [16]:
m.meth.__dict__


Out[16]:
{'material': 'rice'}

设计模式

如果小伙伴想要更进一步了解软件开发的知识,设计模式是没有办法回避的主题。本次培训只给大家介绍两个常见的示例:

抽象工厂有一个优点,在使用工厂方法时从用户视角通常是看不到的,那就是抽象工厂能够通过改变激活的工厂方法动态地(运行时)改变应用行为。一个经典例子是能够让用户在使用应用时改变应用的观感(比如,Apple风格和Windows风格等),而不需要终止应用然后重新启动。


In [28]:
class Frog:

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

    def __str__(self):
        return self.name

    def interact_with(self, obstacle):
        print('{} the Frog encounters {} and {}!'.format(self,
        obstacle, obstacle.action()))


class Bug:

    def __str__(self):
        return 'a bug'

    def action(self):
        return 'eats it'


class FrogWorld:

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

    def __str__(self):
        return '\n\n\t------ Frog World -------'

    def make_character(self):
        return Frog(self.player_name)

    def make_obstacle(self):
        return Bug()

######################### 分割线,两个不同的两组类 #############################

class Wizard:

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

    def __str__(self):
        return self.name


    def interact_with(self, obstacle):
        print(
            '{} the Wizard battles against {} and {}!'.format(
            self,
            obstacle,
            obstacle.action()))


class Ork:

    def __str__(self):
        return 'an evil ork'

    def action(self):
        return 'kills it'


class WizardWorld:

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

    def __str__(self):
        return '\n\n\t------ Wizard World -------'

    def make_character(self):
        return Wizard(self.player_name)

    def make_obstacle(self):
        return Ork()

class GameEnvironment:

    def __init__(self, factory):
        self.hero = factory.make_character()
        self.obstacle = factory.make_obstacle()

    def play(self):
        self.hero.interact_with(self.obstacle)

######################### 以上是两组类的不同实现 ###############################        

def validate_age(name):
    try:
        age = input('Welcome {}. How old are you? '.format(name))
        age = int(age)
    except ValueError as err:
        print("Age {} is invalid, please try again...".format(age))
        return (False, age)
    return (True, age)

def main():
    name = input("Hello. What's your name? ")
    valid_input = False
    while not valid_input:
        valid_input, age = validate_age(name) # 验证判断输入是否正确,而且判断输入的年龄
    game = FrogWorld if age < 18 else WizardWorld# 通过年龄判断应该使用Frogworld还是wizardworld
    environment = GameEnvironment(game(name))
    environment.play()

main()


# Hello. What's your name? Nick
# Welcome Nick. How old are you? 17


#     ------ Frog World -------
# Nick the Frog encounters a bug and eats it!


Hello. What's your name? oliver
Welcome oliver. How old are you? 12


	------ Frog World -------
oliver the Frog encounters a bug and eats it!

本示例中,判断输入的年龄来选择实例化frogworld还是wizardworld,然后直接调用play方法。

策略模式(Strategy pattern)鼓励使用多种算法来解决一个问题,其杀手级特性是能够在运行时透明地切换算法(客户端代码对变化尤感知)。因此,如果你有两种算法,并且知道其中一种对少量输入效果更好,另一种对大量输入效果更好,则可以使用策略模式在运行时基于输入数据决定使用哪种算法。


In [54]:
import types


class StrategyExample:

    def __init__(self, func=None):
        self.name = 'Strategy Example 0'
        if func is not None:
            self.execute = types.MethodType(func, self)# MethodType: The type of methods of user-defined class instances.
            print(self.execute)
            
    def execute(self):
        print(self.name)


def execute_replacement1(self):
    print(self.name + ' from execute 1')


def execute_replacement2(self):
    print(self.name + ' from execute 2')


if __name__ == '__main__':
    strat0 = StrategyExample()

    strat1 = StrategyExample(execute_replacement1)
    strat1.name = 'Strategy Example 1'

    strat2 = StrategyExample(execute_replacement2)
    strat2.name = 'Strategy Example 2'

    strat0.execute()
    strat1.execute()
    strat2.execute()


<bound method execute_replacement1 of <__main__.StrategyExample object at 0x10682d160>>
<bound method execute_replacement2 of <__main__.StrategyExample object at 0x10682c8d0>>
Strategy Example 0
Strategy Example 1 from execute 1
Strategy Example 2 from execute 2

可以看到,这两个例子都是非常简单的,设计模式就是这样:完全都是经验的总结。使用python实现设计模式尤其简单,很多时候会发现自己组织实现了几个类,就是用到了好几种模式。如果小伙伴没有计划从事软件开发,不掌握也没有关系,反之,则必须要有所了解。

GIL

Global Interpreter lock: Python解释器在同一时刻只能运行在一个线程之中。关于这方面的内容,还是参考David beazley相关的演讲:http://www.dabeaz.com/talks.html


In [ ]:
import threading


def worker(num):
    """thread worker function"""
    print('Worker: %s' % num)


threads = []
for i in range(5):
    t = threading.Thread(target=worker, args=(i,))
    threads.append(t)
    t.start()

对于多线程的问题,基本可以到此为止了,因为实在是不推荐使用,因为里面需要了解的东西实在太多了,一不小心,反而性能更糟糕。


In [ ]:

装饰器

装饰器是一种特殊的函数,实现的细节非常套路,但是一旦用好了,可以大大简化程序的设计,减少很多重复代码,并且使功能增强


In [25]:
import time
from functools import wraps

def timethis(func):
    '''
    Decorator that reports the execution time.
    '''
    #@wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

注意上面的示例中使用了标准库functools中自带的wraps方法作为装饰器,用来装饰wrapper函数,这样做可以保留被装饰函数中的元信息


In [26]:
@timethis
def countdown(n):
    '''
    Counts down
    '''
    while n > 0:
        n -= 1
countdown(100000)


countdown 0.007150888442993164

In [27]:
countdown.__name__


Out[27]:
'wrapper'

In [28]:
countdown.__doc__

In [29]:
countdown.__annotations__


Out[29]:
{}

一个装饰器就是一个函数,它接受一个函数作为参数并返回一个新的函数。


In [30]:
@timethis
def countdown(n):
    pass

跟像下面这样写其实效果是一样的:


In [31]:
def countdown(n):
    pass
countdown = timethis(countdown)

把装饰器定义成类


In [44]:
import types
import time
from functools import wraps

class thistime:
    def __init__(self, func):
        wraps(func)(self)
        self.ncalls = 0
        
    def __call__(self, *args, **kwargs):# 通过这里实现装饰器的功能
        self.ncalls += 1
        start = time.time()
        f =  self.__wrapped__(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return f
    
    def __get__(self, instance, cls):
        if instance is None:
            return self
        else:
            return types.MethodType(self, instance)

In [ ]:
# 这里实现类装饰器的关键在于 __call__ 函数,如果小伙伴觉得这种写法难以理解,也可以不用这样写,毕竟python还是有一定的灵活度

In [45]:
@thistime
def countdown(n):
    while n > 0:
        n -= 1
countdown(100)


func 5.9604644775390625e-06

使用wrap 方法


In [32]:
import time
from functools import wraps
def timethis(func):
    '''
    Decorator that reports the execution time.
    '''
    @wraps(func)## 最好默认都使用这个wraps
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        end = time.time()
        print(func.__name__, end-start)
        return result
    return wrapper

In [33]:
@timethis
def countdown(n):
    '''
    Counts down
    '''
    while n > 0:
        n -= 1
countdown(100000)


countdown 0.007012128829956055

In [34]:
countdown.__name__


Out[34]:
'countdown'

In [35]:
countdown.__doc__


Out[35]:
'\n    Counts down\n    '

In [36]:
countdown.__annotations__


Out[36]:
{}

带参数的装饰器


In [37]:
from functools import wraps
import logging

def logged(level, name=None, message=None):
    """
    Add logging to a function. level is the logging
    level, name is the logger name, and message is the
    log message. If name and message aren't specified,
    they default to the function's module and name.
    """
    def decorate(func):
        logname = name if name else func.__module__
        log = logging.getLogger(logname)
        logmsg = message if message else func.__name__

        @wraps(func)
        def wrapper(*args, **kwargs):
            log.log(level, logmsg)
            return func(*args, **kwargs)
        return wrapper
    return decorate

# Example use
@logged(logging.CRITICAL)
def add(x, y):
    return x + y

@logged(logging.CRITICAL, 'example', 'this is very important')
def spam_func():
    print('Spam!')

初看起来,这种实现看上去很复杂,但是核心思想很简单。 最外层的函数 logged() 接受参数并将它们作用在内部的装饰器函数上面。 内层的函数 decorate() 接受一个函数作为参数,然后在函数上面放置一个包装器。 这里的关键点是包装器是可以使用传递给 logged() 的参数的。


In [38]:
add(3,4)


add
Out[38]:
7

In [39]:
spam_func()


this is very important
Spam!

In [42]:
@decorator(x, y, z)
def func(a, b):
    pass


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-42-3b1312246aef> in <module>()
----> 1 @decorator(x, y, z)
      2 def func(a, b):
      3     pass

NameError: name 'decorator' is not defined

装饰器处理过程跟下面的调用是等效的:


In [41]:
def func(a, b):
    pass
func = decorator(x, y, z)(func)


---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-41-ec3f7cde849d> in <module>()
      1 def func(a, b):
      2     pass
----> 3 func = decorator(x, y, z)(func)

NameError: name 'decorator' is not defined

装饰器是属于比较进阶的Python知识了,如果小伙伴不是想要从事python开发的工作,这个可以不用掌握,但是还是需要了解这个语法在做什么。

classmethod, staticmethod


In [5]:
import time
from functools import wraps

# A simple decorator
def timethis(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        start = time.time()
        r = func(*args, **kwargs)
        end = time.time()
        print(end-start)
        return r
    return wrapper

# Class illustrating application of the decorator to different kinds of methods
class Spam:
    @timethis
    def instance_method(self, n):
        print(self, n)
        while n > 0:
            n -= 1

    @classmethod
    @timethis
    def class_method(cls, n):
        print(cls, n)
        while n > 0:
            n -= 1

    @staticmethod
    @timethis
    def static_method(n):
        print(n)
        while n > 0:
            n -= 1

@classmethod 和 @staticmethod 实际上并不会创建可直接调用的对象, 而是创建特殊的描述器对象


In [6]:
s = Spam()
s.instance_method(10)


<__main__.Spam object at 0x1050520b8> 10
0.0005021095275878906

In [9]:
Spam.class_method(1000) #classmethod 可以是直接通过类名称调用


<class '__main__.Spam'> 1000
0.0006039142608642578

In [8]:
Spam.static_method(1000) #严格的讲,staticmethod方法和该方法所在的类没有什么关系,只是为了代码管理方便才放到一起


1000
0.0006861686706542969

In [61]:
s.class_method(11)


<class '__main__.Spam'> 11
0.0008409023284912109

还有一点就是要注意装饰器的叠加,其实这个也很好理解,就是下面的装饰器再被上面的装饰器函数执行一遍

还有一点非常重要:在实际的项目中,装饰器的定义一定要在一个单独的py文件中。