2020-04-08

0 目录

一、模块
  - 举例子
  - 变量类型
二、面向对象初级
    - 类&实例
    - 访问限制
    - 继承多态
    - 获取对象实例信息(属性方法)
    - 实例属性vs类属性
三、面向对象高级
    - __slots__
    - @property

一、模块

  • 举个例子
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

' a test module '  # 可以用moduleName.__doc__访问

__author__ = 'Michael Liao' # 可以用moduleName.__author__访问

import sys

def test():
    args = sys.argv
    if len(args)==1:
        print('Hello, world!')
    elif len(args)==2:
        print('Hello, %s!' % args[1])
    else:
        print('Too many arguments!')

if __name__=='__main__':  # 当在命令行执行此脚本时,会将特殊变__name__的置为__main__, 其他模块导入时,if判断会失效
    test()
  • 变量类型:
    __xxx__ : 特殊变量,在类中的特殊变量是可以被直接访问的,
    _xxx__xxx: 这样的函数或者变量称为“非公开的(private)”, 不应该被直接饮用,比如_abc

这种private变量或者函数不应该被引用,他们是有什么作用呢?

def _private_1(name):
    return 'Hello, %s' % name

def _private_2(name):
    return 'Hi, %s' % name

def greeting(name):
    if len(name) > 3:
        return _private_1(name)
    else:
        return _private_2(name)

当调用greeting的时候,用内部逻辑把private隐藏起来,这样调用greeting就不用关心其内部的private函数细节,作用是“代码封装和抽象”

二、面向对象初级

class Student(object):
    pass
  • 和普通方法相比,其内部的函数,第一个参数永远是'self', 表示创建的实例本身,其他使用都相同
  • 类&实例(数据封装)
    # 优点1. 从外部看Student, 实例化只需要提供name和age,关于如何打印,已经被类封装到内部,调用容易,但是不知道内部细节
    # 优点2: 可以给Student类新增其他更多方法
    class Student(object):
        def __ini__(self, name, score): # 每当有一个类被实例化的时候,都会调用一次__init__方法
            self.name = name
            self.score = score
        def print_score(self):
            print("%s, %s" % (self.name, self.score))
    bart = Student("alex", 50)
    bart.print_score()
    既然这个实例内部本身就能访问到对应数据,就不用讲print_score写到外面
    错误示例如下:
    class Student(object):
        def __ini__(self, name, score):
            self.name = name
            self.score = score
    bart = Student("alex", 50)
    def print_score(std):
        print('%s: %s' % (std.name, std.score))
    print_score(bart)
  • 访问限制

    # 1. 为了避免外部代码随意修改实例的name和score,使用__命名的方式来限制实例对属性的直接访问, 从外部直接访问bart.__name会报错
    # 2. 如果要访问或者修改怎么办?——通过方法来获取
    # 3. 如果我有两个属性, 一个是_name, 另一个是__name, 这两个属性什么意思呢?
    # - _name这个实例变量在外部是可以访问的,但是警告你不要随意访问
    # - __name这个实例变量在外部是不能访问的,但是python解释器会把双下划綫的变量修改为_Student__name, 所以在外面还是可以访问的
    # 4. 那bart.name = "alex"可以直接修改啊,为什么要写一个方法来修改它?岂不是很浪费?
    # 答:在方法中,可以对参数做检查,避免无效的参数传入
    class Student(object):
    def __init__(self, name, score):
        self.__name = name
        self.__score = score
    
    def print_score(self):
        print('%s: %s' % (self.__name, self.__score))
    
    def get_name(self):
        return self.__name
    
    def set_name(self, name):
        if len(name) > 100 or len(name) < 0:
          raise ValueError('bad name len')
        else:
            self.__name = name
  • 继承和多态
    # 1. 继承:子类会继承父类的所有方法和功能,  子类可以继续扩展(覆盖)父类方法
    # 2. 多态: 有关于如何理解多态,参考这里: https://www.liaoxuefeng.com/wiki/1016959663602400/1017497232674368
    # 3. 一个class就是一种数据类型,比如list,dict, str等
    class Animal(object):
    def run(self):
        print("animal can run!")
    class Dog(Animal):
    def run(self):
        print("Dog is Running")
    class Cat(Animal):
        print("Cat is Running")
    dog = Dog()
    dog.run()
    # 多态
    a = list()
    b = Animal()
    c = Dog()
    isinstance(a, list)
    isinstance(b, Animal)
    isinstance(c, Dog)
    isinstance(c, Animal)
    # 结果都为True
    # 多态到底哪里好?
    def run_twice(animal):
    animal.run()
    animal.run()
    >>>run_twice(Animal())
    "animal can run!"
    "animal can run!"
    >>>run_twice(Dog())
    "Dog is Running"
    "Dog is Running"
    >>>run_twice(Cat())
    "Cat is Running"
    "Cat is Running"
    # 总结多态好处
    # 1. 当你一个函数需要传入Dog, Cat, Tortoise...时,只需要接受Animal类型就可以了,因为他们都是Animal
    # 2. 所以: 函数(run_twice)传入的任意类型,只要是animal的类或者子类,就会自动调用时机类型的run()方法,这就是多态的意思
    # 3. 对扩展开放: 允许新增Animal子类:
    # 4. 对修改封闭: 不需要修改依赖Animal类型的run_twice()等函数
  • 获取对象(实例)信息
    # 1. 使用type() # 获取对象(实例)的类型信息
    # 2. 使用isinstance() # 判断实例是否为某个类型
    # 3. 使用dir()  # 返回对象的所有属性和方法
    # 4. hasattr(obj, 'x'), setattr(obj, 'y', 19), getattr(obj, 'y')
    >>>type('stringexample')
    >>>type(Dog())
    >>>type(123)
    >>>def fn():
    >>>  pass
    >>>type(fn) == types.FunctionType
    >>>type(abs) == types.BuiltinFunctionType
    >>>type(lambda x: x*2) == types.LambdaType
    >>>type((x for x in range(10)))==types.GeneratorType
    # isinstance, 假设继承关系为object -> Animal -> Dog -> Husky
    >>>  isinstance(h, Animal)
    >>>  isinstance('a', str)
    >>>  isinstance(b'a', bytes)
    >>>  isinstance((1, 2, 3), (list, tuple)) // 是否是list或者tuple
    >>>  dir('abc')
    ['__add__', '__class__',..., '__subclasshook__', 'capitalize', 'casefold',..., 'zfill']
    >>> len('abc') == 'abc'.__len__() // len()方法其实就是调用对象的__len__()方法,两个等价
    # 对象的属性和方法测试
    >>> class MyObject(object):
    ...     def __init__(self):
    ...         self.x = 9
    ...     def power(self):
    ...         return self.x * self.x
    ...
    >>> obj = MyObject()
    >>> hasattr(obj, 'x') # 是否有属性'x'
    >>> setattr(obj, 'y', 19) # 设置属性'y', 值为9
    >>> getattr(obj, 'y', 100) # 获取obj对象的'y'属性值,如果没有则默认为100
    >>> obj.y
    19
  • 实例属性 vs 类属性
    # 1. python是动态语言,根据类创建的实例,可以任意绑定属性, 给实例绑定属性的方法是通过实例变量,或者通过self属性
    # 2. 实例属性会覆盖类属性,不要对实例属性和类属性使用相同的名字
    class Student(object):
    def __init__(self, name):
        self.name = name
    s = Student('Bob')
    s.score = 90
    # 如何给Student类绑定属性?
    class Student(object):
    name = "Student"
    # 类属性归类所有,但是其所有实例都可访问
    >>> s = Student()
    >>> print(s.name)
    >>> print(Student.name) # 如果不是类属性,这个会报错
    Student

    三、面向对象高级

  • __slots__
    # 1. 通过MethodType给实例绑定方法
    # 2. 通过动态方法,给class绑定方法
    # 3. 通过在类中定义__slots__来限制实例能够添加的属性
    # 4. __slots__仅对当前类生效,对其子类不生效, 子类如果也有__slots__, 那么其子类的限制就是子类+父类的总和
    class Student(object):
    pass
    s = Student()
    s.name = "Michael"
    def set_age(self, age):
    self.age = age
    from types import MethodType
    s.set_age = MethodType(set_age, s) # 给实例绑定一个方法
    s.set_age(25)
    def set_score(self, score):
    self.score = score
    >>> Student.set_score = set_score # 给class绑定一个方法
    >>> s.set_score(100)
    >>> s.score
    >>> class Student(object):
    >>>   __slots__ = ("name", "age") # 用tuple定义允许绑定的属性名称
    >>> s = Student()
    >>> s.name = "Michael" # 可以绑定
    >>> s.score = 99 # 因为啥偶先,所以会报AttributeError
    Traceback (most recent call last):
    File "", line 1, in 
    AttributeError: 'Student' object has no attribute 'score'
  • @property
# 在类中直接设置属性,可以将属性暴露出去,但是没办法检查参数,导致该属性可以随意被更改
# 为了限制属性,我们用set_score()的方式来设置成绩,在通过get_score()方法来获取
>>> class Student(object):
>>>     def get_score(self):
>>>         return self._score
>>>     def set_score(self, value):
>>>         if not instance(value, int):
>>>             raise ValueError('score must be an int')
>>>         if value < 0 or value > 100:
>>>             raise ValueError('score must in 0-100')
>>>         self._score = score
# 但是上面这种又有点复杂, 我要既能检查参数,又可以用“类似属性”这种简单的方式来访问类的变量
# 类中,@property这个内置的装饰器就是讲方法变成属性来调用的
>>> class Student(object):
>>>     @property  #将score这个getter方法变成属性,只需要加上@property这个装饰器即可
>>>     def score(self):
>>>         return self._score
>>>                             
>>>     @score.setter  # @property又提供创建另一个装饰器@score.setter, 它负责把一个setter方法变成属性赋值,于是我们就有一个可控的属性操作
>>>     def score(self,value):
>>>         if not instance(value, int):
>>>             raise ValueError('score must be an int')
>>>         if value < 0 or value > 100:
>>>             raise ValueError('score must in 0-100')
>>>         self._score = value
>>> s = Student()
>>> s.score = 60
>>> s.score
60
>>> s.score = 9999
这里会触发ValueError异常
# 注意: 如果只给一个getter方法设置了@property,不设置对应的setter,那么这个方法就是个“只读属性”
>>> class Student(object):
>>>    @property
>>>     def birth(self):
>>>         return self._birth
>>> 
>>>     @birth.setter
>>>     def birth(self, value):
>>>         self._birth = value
>>> 
>>>     @property
>>>     def age(self):
>>>         return 2015 - self._birth
  • 多重继承
    参考这里:
  • 定制类(python class中的特殊函数)
    # __str__
    # 定义好__str__()方法,用于返回一个好看的字符串
    class Student(object):
    def __init__(self,name):
            self.name = name
        def __str__(self):
            return "student obj (name: %s)" % self.name
    >>> print(Student('Michael')
    student obj (name: Michael) # 如果没有这个__str__就会返回一个这个 `<__main__.Student object at 0x109afb190>`
    # __repr__
    # 上面的还需要print方法才能打印出来
    class Student(object):
    def __init__(self, name):
            self.name = name
        def __str__(self):
            return 'Student obj (name=%s)' % self.name
        __repr__ = __str__
    # __iter__
    # 类的创建本身就是一个type的创建, 就比如列表也是一种type
    # 那么如果你想让你的类能够像列表一样被循环,for ... in 这种,就需要实现一个__iter__()方法
  • 使用枚举类
  • 使用元类