本文介绍: 笔者的的SailWorks模块包含离线分析功能。离线分析的后台实现,包含调度引擎、执行引擎、计算引擎和存储引擎。计算和存储引擎由Hive提供,调度引擎和执行引擎由我们自己实现。调度引擎根据DAG图和调度计划,安排执行顺序,监控执行过程。执行引擎接收调度引擎安排的任务,向Yarn申请容器,在容器中执行具体的任务。在容器中执行的任务我们是用Python语言实现的。在实现这个组建时,笔者是对着python的基础语法教程,边学边写。

1. 背景

笔者的大数据平台XSailboat的SailWorks模块包含离线分析功能。离线分析的后台实现,包含调度引擎、执行引擎、计算引擎和存储引擎。计算和存储引擎由Hive提供,调度引擎和执行引擎由我们自己实现。调度引擎根据DAG图和调度计划,安排执行顺序,监控执行过程。执行引擎接收调度引擎安排的任务,向Yarn申请容器,在容器中执行具体的任务。

在容器中执行的任务我们是用Python语言实现的。在实现这个组建时,笔者是对着python的基础语法教程,边学边写。基本实现离线分析的功能之后,就开始做项目,做实时计算,开发大数据平台的其它功能模块,一转眼已经过去将近两年,现回过头来继续完善离线分析功能,对执行引擎中的python执行组件进一步完善,扩展。为此再进阶一步系统学习一下Python,在最近将写一些Python相关的笔记。

2 . 类(Class)

这里我们不去细究Python底层到底是怎么做的,主要是从现象总结一些规律,以更方便记住。欲看底层逻辑原理,可以看此B站视频【python】你知道定义class背后的机制和原理嘛?当你定义class的时候,python实际运行了什么呢?

先看一下下面的代码:

class B:
    def __init__(self):
        print('类型B的对象实例化')


class A:
    f1 = '字段1'

    f3 = B()

    def __init__(self):
        self.f2 = '字段2'


# 此处已经输出:类型B的对象实例化
a = A()			# 输出:类型A的对象实例化
a.f1 = '字段1改'
print(a.f1)     # 输出:字段1改
print(A.f1)     # 输出:字段1
print(A.f2)     # 报错,type object 'A' has no attribute 'f2'

从中我们可以总结出以下规律:

  1. class类也是一种对象,成员属性会成为这个类对象的属性。
  2. 类的加载过程中,类对象的属性会被初始化,且先与构造函数__init__被调用。
  3. 实例化对象,它有自己的属性表,在实例化过程中会浅clone当前类对象属性取值。
  4. 实例对象的动态增加的属性,不纳入类属性中。
class A:

    def __init__(self, name: str):
        self.name = name
        print(f'类型A的对象实例化{name}')

    def saiHi(self):
        print(f'你好,{self.name}')


a1 = A('a1')
a2 = type(a1)('a2')		# 使用a1的类型A,构建一个对象
print(A.__dict__)
'''
输出:
{'__module__': '__main__', '__init__': <function A.__init__ at 0x105bce040>, 'saiHi': <function A.saiHi at 0x105bce0d0>, '__dict__': <attribute '__dict__' of 'A' objects>, '__weakref__': <attribute '__weakref__' of 'A' objects>, '__doc__': None}
'''


def m0(self, name):
    self.name = name
    print(f'类型B的对象实例化{name}')


def m1(self):
    print(f'你好,{self.name}')


b_class = {
    '__init__': m0 ,
    'sayHi': m1
}


# 仿照A,动态构建出一个和A功能相同的类型B出来
B = type('B', (), b_class)
b = B('b1')
b.sayHi()

3. 描述器(Descriptor)

官方文档:《描述器使用指南》

  1. descriptor 就是任何一个定义了__get__()、__set__()或__delete__() 的对象。
  2. 可选地,描述器可以具有 set_name() 方法。这仅在描述器需要知道创建它的类或分配给它的类变量名称时使用。(即使该类不是> 描述器,只要此方法存在就会调用。)
  3. 在属性查找期间,描述器由点运算符调用。
  4. 描述器仅在用作类变量时起作用。放入实例时,它们将失效。
  5. 描述器的主要目的是提供一个挂钩,允许存储在类变量中的对象控制在属性查找期间发生的情况。

3.1 第2条实践

class Name:

    def __set_name__(self, owner, name):
        print(f'我的参数名是{name}')


class A:

    myNameA1 = Name()				# 输出:我的参数名是myNameA1

    def __init__(self):
	    self.myNameA1 = Name()		# 没有输出
        self.myNameA2 = Name()		# 没有输出


myName = Name()						# 没有输出

说明__set_name__只有当其在类的构造和初始化过程中,被构造出来赋值给类的成员变量时,此方法才会被调用。

3.2 内部机制探索

class Name:

    def __get__(self, instance, owner):
    	print(f'self:{self},instance:{instance} , owner:{owner}')
        return '张三'


class A:

    myName = Name()


a = A()
a.myName = '李四'
print(a.myName)			# 1.输出:李四
Name.__set__ = lambda self, instance, value: None
print(a.myName)			# 2.输出:张三
'''
输出:
self:<__main__.Name object at 0x10ce71d30>,instance:<__main__.A object at 0x10ce715b0> , owner:<class '__main__.A'>
张三
'''
A.myName = '王五'
print(a.myName)			# 3.输出:李四
  1. 之所以输出“李四”,是因为a对象,它的成员属性myName被设置成了“李四”,当前a.myName属性就是字符串‘李四’
  2. 在给Name设置了__set__方法之后,之所以输出“张三”,是因为python内部,发现A类的成员属性myName是Name类型,它是一个同时具有__get__和__set__方法的的描述器,它就会去调用描述器对象A.myName的__get__方法。
  3. 之所以会输出‘李四’,是因为此时A.myName是字符串“王五”,它不是一个描述器。所以它就会直接取出a.myName的值字符串“王五”。这也说明了描述器的第4点特性,为什么在实例中构造的描述器对象并被赋值给了实例对象的成员变量,描述器是无法起作用的。

4. 装饰器(decorator)

python的装饰器可以用在函数上(函数装饰器),也可以用在类上(类装饰器)。它的本质是

a = func(a)

即把一个函数或类映射成另一个函数或类(甚至是常量),并且赋值给原来的函数名或类名变量。在python中,函数名或类名,它就是一个指向函数对象或类对象的变量名,所以它可被重新赋值。

python中,装饰器之所以把一个函数或类映射成另一个函数或类之后,又赋值给了原来的函数名或类名变量,目的就是为了让人/代码逻辑无感/无需修改这个函数或类的相关使用代码的前提下,修改函数/类的成员方法的逻辑,修改类的一些特性等。所以经过映射得到的新的函数或类,与被装饰的类,形式上应该是兼容的。

这是一个给函数运行时长计时的例子:

import time


def clock(func):

    def wrapper(*args, **kwargs):
        start = time.time()
        func(*args, *kwargs)
        print(f'耗时:{time.time() - start}')

    return wrapper


@clock
def test():
    print('开始干活')
    time.sleep(2)
    print('活干完了')


test()

装饰器的实现,不仅可以是函数,还可以是实现了__call__方法的类。

4.1 函数和类形式上的等价性

在python中,一切皆对象,函数是对象,类也是对象。看下面的代码:

def f():
    print("函数做了一些事情")


class F:
    def __init__(self):
        pass

    def __call__(self, *args, **kwargs):
        print("方法做了一些事情")


f()				# 输出:函数做了一些事情 
f1 = F()
f1()			# 输出:方法做了一些事情
# 等价于F()()

我们可以看出f()和f1()(即F()())在形式上是一样的。再考虑复杂点,带参数的情况:

class F:
    def __init__(self, attr):
        self.attr = attr

    def __call__(self, *args, **kwargs):
        print(f"方法做了一些事情,属性是{self.attr}")


def f(attr):

    def fi():
        print(f"方法做了一些事情,属性是{attr}")

    return fi


F('python')()			# 方法做了一些事情,属性是python
f('python')()			# 方法做了一些事情,属性是python

原文地址:https://blog.csdn.net/OkGogooXSailboat/article/details/135861992

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。

如若转载,请注明出处:http://www.7code.cn/show_64559.html

如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注