zoukankan      html  css  js  c++  java
  • python学习笔记_序

    说明

    这里的python笔记也是之前留下的,看了下时间,大概是今年寒假期间学的,主要是跟着廖大的教程来。也是半途而废的计划啊(干了多少半途而废的事情)。


    教程地址:Python教程--廖雪峰

    数据类型

    还没学之前就有所耳闻,Python是动态语言,不需要定义类型名的,语法上已经将这一块封装好了。

    除了整形、浮点型和字符串外,Python也有布尔值(有特殊的运算符andor),“常量”(Python的常量其实是一个变量)。但最大的不同点是,Python有独特的“列表”和“字典”类型。

    列表:分为list(列表)和tuple(元组),list是可变的,而tuple是不可变的。list有点像一个数组但是内部保存的元素可以是多种且不同的数据类型。tuple也很类似,但是它内部的元素必须确定,即不可改变。
    有一个比较容易搞混的例子:t = ('a', 'b', ['A', 'B']),它是正确的。在tuple的指向里,三个元素的位置是确定不变的;在list中,它储存元素的空间是可以更换不同的元素的。


    dict:当看到dict的特性时,我立马想到数据结构里的哈希表,查了一下确实是如此。那就很好理解了,key值是索引哈希值所在位置的钥匙。另一个方面,dict的特性就像查字典那样,时间永远是常量级的,典型的空间换时间思想。一个很重要的地方时,key必须是一个不可变的数据类型,也就是说list是不行的,某种情况下的tuple也是不行的(上面的例子)。
    set:从词义上理解就很清晰了,就是一个集合。事实上它所储存的元素是不可重复的。当然,dict的特性也保证了key所对应的value也是不可重复的,要不然查找效率就不是常量级了。

    函数

    函数定义

    函数没有太大的变化,记录一下之前没见过的。

    isinstance():检查参数类型是否合格。
    返回多个值:C/C++是不允许返回多个值的,如果实在需要,只能用数组指针。Python之所以可以返回多个值,其实是tuple的作用。
    import XX:类似于头文件

    函数参数

    Python的参数有点复杂,它不需要定义参数是哪种数据类型(一开始学真的很不习惯,被C虐惯了),但可以根据参数的作用来区分参数类型,一共有5类:必选、默认、可变、关键词、命名关键词。并且参数的先后顺序要严格按照这个来,否则会产生歧义。如调用fun(a, b = 1, c)传入(1, 2)时,我们不知道应该是报错还是按照调用(1, 1, 2)来。

    必选:也叫位置参数,调用时必须有一个值占位
    默认:提前设置好参数的值,如果调用时没有值占位,那么就使用默认值
    可变:传入的参数个数是可变的,在参数前加一个*,看起来有点像指针,函数内部接收到的其实是一个tuple,因此代码不会变化
    关键字:**kw,允许传入任意个含参数名的参数,这些关键字参数在函数内部自动组装成一个dict。
    命名关键字:限制关键字参数名字,需要用到特殊分隔符 * , * 后面的参数被视为命名关键字;如果函数中有可变参数,就不需要再使用分隔符了

    对于任意函数,都可以通过类似func(*args, **kw)的形式调用它,无论它的参数是如何定义的。

    大神的小结:

    Python的函数具有非常灵活的参数形态,既可以实现简单的调用,又可以传入非常复杂的参数。
    默认参数一定要用不可变对象,如果是可变对象,程序运行时会有逻辑错误!
    要注意定义可变参数和关键字参数的语法:
    args是可变参数,args接收的是一个tuple;
    **kw是关键字参数,kw接收的是一个dict。
    以及调用函数时如何传入可变参数和关键字参数的语法:
    可变参数既可以直接传入:func(1, 2, 3),又可以先组装list或tuple,再通过
    args传入:func((1, 2, 3));
    关键字参数既可以直接传入:func(a=1, b=2),又可以先组装dict,再通过 * * kw传入:func(
    * {'a': 1, 'b': 2})。
    使用* args和**kw是Python的习惯写法,当然也可以用其他参数名,但最好使用习惯用法。
    命名的关键字参数是为了限制调用者可以传入的参数名,同时可以提供默认值。
    定义命名的关键字参数在没有可变参数的情况下不要忘了写分隔符*,否则定义的将是位置参数。

    函数递归

    讲到一个尾递归的东西,可总结为:返回值只能是函数自身而不能是一个表达式
    尾递归很容易改成循环的形式,但是很多编辑器没有做到。印象中C好像是做到了?

    高级特性

    在Python中,代码不是越多越好,而是越少越好。代码不是越复杂越好,而是越简单越好。

    切片

    用法是[x:x],提取list或tuple里这一部分的数据,十分方便

    迭代

    语法是 for ... in ...,不仅可以迭代list或tuple,还可以操作dict
    dict内部元素储存是无序的,所以迭代的结果可能会和储存的顺序不一样。另外,dict迭代默认情况下是key,可用for value in d.value() 迭代value,还可以for k, v in d.item() 来同时迭代key和value。
    字符串迭代,要实现同时索引对和元素对的迭代,可以使用内置的enumerate()函数。
    判断是否为可迭代对象,通过collections的Iterable类型来判断。

    列表生成式

    格式:[元素或表达式 , 循环式]
    循环式还可以用两层循环。
    运用列表生成式,可以写出非常简洁的代码。例如,列出当前目录下的所有文件和目录名,可以通过一行代码实现:

    >>> import os # 导入os模块,模块的概念后面讲到
    >>> [d for d in os.listdir('.')] # os.listdir可以列出文件和目录
    

    确实不愧为“人生苦短,我用python”,脑海中想着如果要用C的话,先不论要设多少个边界条件,关键是也不知道怎么写啊……

    生成器generator

    生成器可以看成是一个集成了算法的列表。
    要比较列表生成式和生成器的差别,可以看一段代码:

    >>> L = [x * x for x in range(10)]
    >>> L
    [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
    >>> g = (x * x for x in range(10))
    >>> g
    <generator object <genexpr> at 0x1022ef630>
    

    因而生成器是要通过一边循环一遍计算得到所有的结果的。
    另外,如果在一个函数里添加了yield X,函数就变成了生成器。而yield的作用是输出当前生成器计算所得的X的结果。

    可迭代对象和迭代器

    可迭代对象 Iterable:可以直接作用于for循环的对象,包括集合数据类型和generator
    迭代器 Iterator:可以被next()函数调用并不断下一个值的对象。
    可以isinstance()函数来区分可迭代对象和迭代器。


    函数式编程

    说实话没看明白,不过上网搜索了一下,函数式和命令式同属于面向过程的思维。函数式编程式接近数学计算。只能明白这么多了。

    高阶函数 Higher-order function

    总结起来就是大神说的那么几句话:变量可以指向函数、函数名也是变量、函数可以作为变量传参。

    虽然很多人都说学新语言就要放下固有语言的成见,但是我还是不由自主地对比起C/C++与python在这一点的不同。变量指向函数可以通过函数指针解决,但应该不是一个层次的东西。python的核心是为了实现函数作为参数传递的机制,这是高阶函数的特征。而C/C++并无意做到这一点,它们无法忍受未知的事物作为参数,而一个函数在未被调用之前,很明显会被看作未知事物。

    几个高阶函数

    map: 接受两个参数,第一个函数对象, 一个Interable。map将的函数作用到序列的每个元素,并把结果作为新的Interable。
    reduce: 把一个函数作用在一个序列上,这个函数必须接受两个参数,reduce把结果继续和序列的下一个元素累积计算
    filter: 用于过滤序列。返回值决定保留还是丢弃该元素
    sorted: 排序函数,可以接受一个key函数来实现自定义排序

    返回函数

    即将函数作为结果值返回。这一部分着实有点难以理解,换个名字我就吓了一跳,它就是赫赫有名的“闭包”。

    举教程的例子:

    def lazy_sum(*args):
        def sum():
            ax = 0
            for n in args:
                ax = ax + n
            return ax
        return sum
    
    f = lazy_sum(1, 3, 5, 7, 9)
    

    当我们调用lazy_sum() 时,返回的是求和函数。只有调用函数f 时,才能计算真正求和的结果。

    需要注意的两个地方是:

    1. 当我们调用lazy_sum() 时,每次调用都会返回一个新的函数,即使传入相同的参数,两次调用也是不等价的。
    2. 当一个函数返回了一个函数后,其内部的局部变量还被新函数引用。这时,如果返回函数的返回值使用了循环变量,那么多次调用的函数会共享最后一次变量的值,达不到我们的目的。

    匿名函数

    关键词lambda,就是一个表达式,表达式的值就是返回值。可以把匿名函数赋给一个变量,也可以把它作为返回值返回

    装饰器 decorator

    假如要增强某一个函数的功能,比如说在函数调用前后打印日志,但又不希望改变原函数的定义,可以使用装饰器在代码运行阶段动态增强功能。

    本质上装饰器就是一个返回函数的高阶函数,如:

    def log(func):
        def wrapper(*args, **kw):
            print('call %s():' % func.__name__)
            return func(*args, **kw)
        return wrapper
    
    @log
    def now():
        print('2015-3-25')
    
    输出结果:
    >>> now()
    call now():
    2015-3-25
    

    实质上是实现了now = log(now) 的功能。

    偏函数

    利用functools.partial 帮助创建一个偏函数,这个可以固定某些参数,返回一个新函数,调用这个新函数会更简单。
    创建偏函数时,实际上可以接受函数对象、* args、**kw这三个参数的传入。


    模块

    在python文件中,一个py文件就是一个模块。使用模块可以提高代码的可维护性,其次方便其它地方的引用。python有内置的模块和来自第三方的模块。使用模块还可以避免函数名和变量名冲突。相同名字的函数和变量完全可以分别存在不同的模块中。但是我们自己命名的函数尽量不要与内置函数名冲突。
    为了避免同样的模块名冲突,python引进了按目录来组织模块的方法,称为包。引入了包以后,只要顶层的包名不与别人的冲突,那所有模块都不会与别人冲突。每一个包目录下面都会有一个_init_.py的文件,否则python会把这个目录当成普通目录。

    使用模块

    python的函数和变量可以通过前缀_来改变作用域。正常的变量和函数是公开的,即public,可以直接访问,;而类似_XXX_的变量是特殊变量也可以直接引用,但是有特殊用途,比如_name_;类似_XXX__XXX这样的函数就是非公开的,即private。
    虽然如此,但是在python中其实并没有一种方法可以完全限制住private变量或函数的访问,只是从编程习惯上我们不应该引用它。

    面向对象

    类和实例

    和C++很像,定义类也是通过class关键字。class后紧跟着类名,紧接着是(object),表示这个类是从哪个类继承下来的。
    创建实例是用类+()实现的。可以自由地给一个实例变量绑定一些属性。
    由于类可以起到模板的作用,因此可以通过定义一个特殊的__init__方法,在创建实例的时候,把我们认为必须强制绑定的属性绑上去。__init__方法的第一个参数是self,表示创建的实例本身。
    可以将函数定义在类里,实现数据封装。
    和静态语言不同(如C++),Python允许对实例变量绑定任何数据,也就是说,对于两个实例变量,虽然它们都是同一个类的不同实例,但拥有的变量名称都可能不同。

    访问限制

    如果要让内部属性不被外部访问,可以把属性的名称前面加上两个下划线__。在python中,实例的变量名如果加上了两个下划线,就变成了一个私有变量。
    其实python的私有变量是没有得到像C++那样的机制保护的。事实上,之所以不能直接访问私有变量是因为解释器对外会把__XXX改成_Object__XXX,所以仍然可以通过后者来访问这个私有变量。

    继承和多态

    这一小节对面对对象编程来说是重点。刚学C++时,继承还好理解,多态就有些费解了。正好现在再看看python的继承和多态,两相对比来理解。
    理解继承的时候,一定要想象一棵树(最好是数据结构的那种树),根结点是所有子孙结点的父辈结点。同样的,一棵继承树的根类是下面所有结点的父类,它们共同继承了这个父类的全部功能。
    理解多态时,我是对比重载和多态的相同和不同之处。和C++不同的是,python是不支持函数名重载的,内部对比无从说起。但是在类的继承中,相同的函数名子类可以覆盖父类。在引用子类的实例时,实例的函数是子类所覆盖的那个函数。
    看一个例子来理解会好一点:

    def run_twice(animal):
        animal.run()
        animal.run()
    

    因为继承,所以所有的子类都可以作为animal参数;又因为多态,所以在引用参数的内部函数时其实是引用了覆盖了的函数。

    廖老师的总结:

    这就是多态真正的威力:调用方只管调用,不管细节,而当我们新增一种Animal的子类时,只要确保run()方法编写正确,不用管原来的代码是如何调用的。这就是著名的“开闭”原则:
    1.对扩展开放:允许新增Animal子类;
    2.对修改封闭:不需要修改依赖Animal类型的run_twice()等函数。

    最后是静态语言和动态语言在继承上的不同。静态语言,以C++为例,像上面那个例子,传入的参数必须只能是animal或它的子类;而动态语言,如python,只要那个实例有run()方法,那就可以传入,亦被称为“鸭子类型”。

    获取对象信息

    当我们拿到一个对象的参数时,可以使用type() isinstance() dir() 来知道这个对象是什么类型,有哪些方法。

    type()

    基本类型、指向函数或类的变量都可以用type()判断,返回的是对应的Class()类型。
    如果要判断一个对象是否为函数,可以使用types模块中定义的常量。

    isinstance()

    可用于判断class的类型,告诉我们一个对象是否是某一个类型。
    能用type()判断的都可以用isinstance()判断,而后者还可以判断一个变量是否是某些类型中的一种。如:

    >>>isinstance([1,2,3], (list,tuple))
    True
    
    dir()

    用于获得一个对象的所有属性和方法,返回值是一个包含字符串的list。
    类似__xxx__的属性和方法在Python中都是有特殊用途的,比如__len__方法返回长度。在Python中,如果你调用len()函数试图获取一个对象的长度,实际上,在len()函数内部,它自动去调用该对象的__len__()方法,所以,下面的代码是等价的:

    >>> len('ABC')
    3
    >>> 'ABC'.__len__()
    3
    

    我们自己写的类,如果也想用len(myObj)的话,就自己写一个__len__()方法:

    >>> class MyDog(object):
    ...     def __len__(self):
    ...         return 100
    ...
    >>> dog = MyDog()
    >>> len(dog)
    100
    

    仅仅把属性和方法列出来是不够的,配合getattr() setattr() hasattr,我们可以直接操作一个对象的状态。
    用这些函数,可以测试对象的属性或方法。如果试图获取不存在的属性,会抛出AttributeError的错误;可以传入一个默认参数,如果属性不存在,就返回默认值。

    实例属性和类属性

    看下面这段测试程序:

    >>> class Student(object):
    ...     name = 'Student'
    ...
    >>> s = Student() # 创建实例s
    >>> print(s.name) # 打印name属性,因为实例并没有name属性,所以会继续查找class的name属性
    Student
    >>> print(Student.name) # 打印类的name属性
    Student
    >>> s.name = 'Michael' # 给实例绑定name属性
    >>> print(s.name) # 由于实例属性优先级比类属性高,因此,它会屏蔽掉类的name属性
    Michael
    >>> print(Student.name) # 但是类属性并未消失,用Student.name仍然可以访问
    Student
    >>> del s.name # 如果删除实例的name属性
    >>> print(s.name) # 再次调用s.name,由于实例的name属性没有找到,类的name属性就显示出来了
    Student
    

    类属性可以用作一个类所有对象的全局变量,在统计一些数据的时候特别有用。


    面向对象高级编程

    在C++中,之前那些已经算是基础语法所学的全部了。在python中,面向对象还有很多高级特性。

    使用__slots__

    python中,动态绑定允许我们在程序运行的过程中动态给实例或class加上属性和方法。但是,如果我们想要限制实例的属性,可以定义一个特殊的变量__slots__,来限制该class实例能添加的属性。
    需要注意的是,__slots__定义的属性只能对当前类起作用,对继承的子类是不起作用的。但是,如果子类也定义了__slots__,子类允许定义的属性就是自身的加上父类的。

    使用@propert

    在绑定属性时,为了避免参数值出现不符合常理的情况,我们需要对它进行限制。一种方法是将类的变量类型设置为私有,利用类的方法来访问;但是对于一些情况来说这种方式显得有些麻烦。
    要想达到既能检查参数,又可以用类似属性这样简单的方式来访问类的变量的目的,可以使用python内置的@property装饰器,将一个方法编程属性来调用。如:

    class Student(object):
    
        @property
        def score(self):
            return self._score
    
        @score.setter
        def score(self, value):
            if not isinstance(value, int):
                raise ValueError('score must be an integer!')
            if value < 0 or value > 100:
                raise ValueError('score must between 0 ~ 100!')
            self._score = value
    

    把一个getter方法变成属性,只需要加上@property就可以了;此外,@property又创建了另一个装饰器@score.setter,负责把一个setter方法变成属性赋值。于是我们就有了一个可控的属性操作。

    >>> s = Student()
    >>> s.score = 60 # OK,实际转化为s.set_score(60)
    >>> s.score # OK,实际转化为s.get_score()
    60
    >>> s.score = 9999
    Traceback (most recent call last):
      ...
    ValueError: score must between 0 ~ 100!
    

    通过这个装饰器,我们还可以只定义getter方法,实现只读属性。

    多重继承

    应用到多重继承的设计通常被称为MixIn。MixIn的目的是给一个类增加多个功能,这样在设计类的时候,我们优先考虑通过多重继承来组合多个MixIn的功能。这样一来,我们不需要复杂而庞大的继承链,只要选择组合不同的类的功能,就可以快速构造出所需的子类。
    需要注意的是,只允许单一继承的语言不能使用MixIn设计。

    定制类

    python的class还有许多有特殊作用的函数,可以帮助我们定制类。

    __ str __

    打印一个类的实例时,若是想要按照我们的想法打印,只需要定义__str__方法。如:

    >>> class Student(object):
    ...     def __init__(self, name):
    ...         self.name = name
    ...     def __str__(self):
    ...         return 'Student object (name: %s)' % self.name
    ...
    >>> print(Student('Michael'))
    Student object (name: Michael)
    

    但是若想直接敲变量不用print,这时候直接显示变量调用的不是__str__(),而是__repr__()。两者的区别是前者返回用户看到的字符串,而后者返回程序开发者看到的字符串,即后者是为调试服务的。
    解决的方法就是再定义一个__repr__(),可以直接用赋值语句将__str__()赋给它。

    __ iter __

    如果一个类想被用于for···in···循环,就必须实现__iter__()方法,该方法返回一个迭代对象,然后python的for循环就会不断调用该迭代对象的__next__()方法拿到下一个循环值,知道遇到StopInteration错误退出循环。如:

    class Fib(object):
        def __init__(self):
            self.a, self.b = 0, 1   # 初始化两个计数器a, b
        
        def __iter__(self):
            return self     # 实例本身就是迭代对象,故返回自己
            
        def __next__(self):
            self.a, self.b = self.b, self.a + self.b # 计算下一个值
            if self.a > 100000: # 退出循环的条件
                return StopInteration()
            return self.a       # 返回下一个值
    

    若将fib实例用于for循环:

    >>> for n in Fib():
    ···     print(n)
    ···     
    1
    1
    2
    3
    5
    ···
    46368
    75025
    

    __ getitem __

    Fib实例虽然能作用于for循环,看起来和list有点像,但是把它当作list来使用还是不行的。要表现得像list那样按照下标取出元素,需实现__getitem__()`方法。

    class Fib(object):
        def __getitem__(self, n):
            a, b = 1, 1
            for x in range(n):
                a, b = b, a + b
            return a
    

    如果要实现list的切片方法,需要作判断传入的参数是一个int还是一个slice对象。如果把对象看成dict,__getitem__的参数也可能是一个可以作key的object,例如str。
    与之对应的是__setitem__()方法,把对象视作list或dict来对集合赋值。最后还有一个__delitem__()方法,用于删除某个元素。
    总之,通过上面的方法,我们自己定义的类表现得和python自带的list、tuple和dict没什么区别,这完全归功于动态语言的“鸭子类型”,不需要强制继承某个接口。

    __ getattar __

    正常情况下,当我们调用类的方法或属性时,如果不存在就会报错。写一个__getattar__()方法可以动态返回一个属性。
    只有在没有找到属性的情况下,才调用__getattar__。此外如果调用如s.abc都会返回None,这是因为我们定义的__getattar__默认返回就是None。要让class只响应特定的几个属性,我们就要按照约定,抛出AttributeError的错误。
    这实际上可以把一个类的所有属性和方法全部动态化处理,不需要任何特殊手段。这种完全动态调用的特性可以针对完全动态的情况作调用。

    现在很多网站都搞REST API,比如新浪微博、豆瓣啥的,调用API的URL类似:
    http://api.server/user/friends
    http://api.server/user/timeline/list
    如果要写SDK,给每个URL对应的API都写一个方法,那得累死,而且,API一旦改动,SDK也要改。
    利用完全动态的__getattar__,可以写一个链式调用。

    class Chain(object):
    
        def __init__(self, path=''):
            self._path = path
    
        def __getattr__(self, path):
            return Chain('%s/%s' % (self._path, path))
    
        def __str__(self):
            return self._path
    
        __repr__ = __str__
    
    >>> Chain().status.user.timeline.list
    '/status/user/timeline/list'
    

    还有些REST API会把参数放到URL中,比如GitHub的API:

    GET /users/:user/repos
    

    调用时,需要把:user替换为实际用户名:

    Chain().users('michael').repos
    

    __ call __

    一个对象实例可以有自己的属性和方法,当我们调用实例方法时,我们用instance.method()来调用。如果要想在实例本身上调用,只需要定义一个__call__方法。

    class Student(object):
        def __init__(self, name):
            self.name = name
    
        def __call__(self):
            print('My name is %s.' % self.name)
    

    调用方法如下:

    >>> s = Student('Michael')
    >>> s() # self参数不要传入
    My name is Michael.
    

    __call__()还可以定义参数。对实例进行直接调用就好比对一个函数进行调用一样,所以你完全可以把对象看成一个函数,把函数看成对象。
    但是如何判断一个变量时对象还是函数呢?其实,更多的时候我们需要判断的是一个对象是否能被调用,能被调用的对象就是一个Callable对象。通过callable()函数,我们就可以判断一个对象是否是“可调用”对象。

    使用枚举类

    当我们需要定义大量常量时,可以为这样的枚举类型定义一个class类型,然后,每个常量都是class的一个唯一实例。

    from enum import Enum
    
    Month = Enum('Month', ('Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'))
    

    这样我们就获得了Month类型的枚举类,可以直接使用Month.Jan来引用一个常量,或者枚举它的所有成员。

    for name, member in Month.__members__.items():
        print(name, '=>', member, ',', member.value)
    

    value属性是自动赋给成员的int常量,默认从1开始计数。

    如果需要更精确地控制枚举类型,可以从Enum派生出自定义类:

    from enum import Enum, unique
    
    @unique
    class Weekday(Enum):
        Sun = 0 # Sun的value被设定为0
        Mon = 1
        Tue = 2
        Wed = 3
        Thu = 4
        Fri = 5
        Sat = 6
    

    @unique装饰器可以帮助我们检查保证没有重复。访问这些枚举类型可以有多种方式,既可以用成员名称引用枚举变量,又可以直接根据value的值获得枚举类型。

    总结:Enum可以把一组相关常量定义在一个class中,且class不可变,而且成员可以直接比较。

    使用元类

    type()

    动态语言和静态语言最大的不同,就是函数和类的定义,不是编译时定义的,而是运行时动态创建的。

    比方说我们要定义一个Hello的class,就写一个hello.py的模块。

    class Hello(object):
        def hello(self, name = 'world'):
            print('Hello, %s.', % name)
    

    当Python解释器载入hello模块时,就会依次执行该模块的所有语句,执行结果就是动态创建除一个Hello的class对象,测试如下:

    >>> from hello import Hello
    >>> h = Hello()
    >>> h.hello()
    Hello, world.
    >>> print(type(Hello))
    <class 'type'>
    >>> print(type(h))
    <class 'hello.Hello'>
    

    type()函数可以查看一个类型或变量的类型,Hello是一个class,它的类型就是type,而h是一个实例,它的类型就是class Hello
    我们说class的定义是运行时创建的,而创建class的方法就是使用type()函数。

    type()函数既可以返回一个对象的类型,又可以创建新的类型,比如,我们可以通过type()函数创建出Hello类,而无需通过class Hello(Object)...的定义:

    >>> def fn(self, name = 'world'):  #先定义函数
    ...     print('Hello, %s' % name)
    ...
    >>> Hello = type('Hello', (Object), dict(hello=fn)) #创建Hello class
    >>> h = Hello()
    >>> h.Hello()
    Hello, world
    >>> print(type(Hello))
    <class 'type'>
    >>> print(type(h))
    <class '_main_.Hello'>
    

    要创建一个class对象,type()函数依次传入3个参数:

    1. class的名称;
    2. 继承的父类集合,注意Python支持多重继承,如果只有一个父类,别忘了tuple的元素写法;
    3. class的方法名称与函数绑定,这里我们把函数fn绑定到方法名hello上。

    通过type()函数创建的类和直接写class是完全一样的,因为Python解释器遇到class定义时,仅仅是扫描一下class定义的语法,然后调用type()函数创建出class。
    正常情况下,我们都用class Xxx...来定义类,但是,type()函数也允许我们动态创建出类来,也就是说,动态语言本身支持运行期动态创建类,这和静态语言有非常大的不同,要在静态语言运行期创建类,必须构造源代码字符串再调用编译器,或者借助一些工具生成字节码实现,本质上都是动态编译,会非常复杂。

    metaclass 元类

    除了使用type()动态创建类外,要控制类的创建行为,还可以使用metaclass。
    当我们定义了类以后,就可以根据这个类创建出实例,所以:先定义类,然后创建实例。
    但是如果我们想创建出类呢?那就必须根据metaclass创建出类,所以:先定义metaclass,然后创建类。
    连接起来就是:先定义metaclass,就可以创建类,最后创建实例。
    所以metaclass允许你创建类或修改类。

    后面的……没看懂,先不记了。

  • 相关阅读:
    解决iOS app集成共享QQ场地,微信的朋友,朋友等功能圈,不能采用苹果公司的审计问题
    沙朗新闻发布系统汇总
    Cocos2d-x示例:单点触摸事件
    【UVA272】TEX Quotes
    Android多线程的研究(8)——Java5于Futrue获取线程返回结果
    Spring Assert主张 (参议院检测工具的方法-主张)
    redmine使用汇总redmine软件工程过程
    IIS7构造Gzip压缩
    PHPthinking官方论坛
    javaweb学习总结(十一)——使用Cookie进行会话管理
  • 原文地址:https://www.cnblogs.com/ChanWunsam/p/10018246.html
Copyright © 2011-2022 走看看