Skip to content

Python面向对象三大特性

Python面向对象三大特性:

  • 封装
  • 继承
  • 多态

封装 Encapsulation

封装是使用特殊的语法,对成员属性和成员方法进行包装,限制一些访问和操作,达到保护和隐藏的目的。

封装机制保证了类内部数据结构的完整性,因为使用类的用户无法直接看到类中的数据结构,只能使用类允许公开的数据,很好地避免了外部对内部数据的影响,提高了程序的可维护性。

对一个类实现良好的封装,用户只能借助暴露出来的类方法来访问数据,可以在这些暴露的方法中加入适当的控制逻辑,即可控制用户对类中属性或方法的操作。

对类进行良好的封装,主要是内部使用封装的成员,也提高了代码的复用性。

类成员封装的级别:

  • 公有的(public)
  • 保护的(protected),在Python中并没有实现protected封装,属于开发者的约定俗成。
  • 私有的(private),在Python中private封装是通过改名策略来实现的,并不是真正的私有化。
访问限制 共有的public 受保护的protected 私有的private
在类的内部 OK OK OK
在类的外部 OK No (Python中可以) No

看下面的例子。(参考私有变量Private Variables)

  • name是共有属性,可以在外部调用tom.name。
  • _age是受保护的属性,理论上在外部是不可调用的,但在Python中是可以调用的tom._age
  • __phone是私有属性,在外部是不可调用的,tom.__get_phone()报错“属性不存在”。
  • 对应方法也是类似。
  • 在类的内部对受保护对象和私有对象没有访问限制。_get_age可以调用私有属性__phone
class Person():
    name = 'name'  # public
    _age = 0  # protected
    __phone = 'phone'  # private

    def __init__(self, n, a, p):
        self.name = n
        self._age = a
        self.__phone = p

    def get_name(self):
        print(f'My name is {self.name}')

    def _get_age(self):
        print(f'My age is {self._age}')
        print(f'My age is {self.__phone}')

    def __get_phone(self):
        print(f'My phone is {self.__phone}')


tom = Person('Tom', 18, 12345678)

tom.name
# 'Tom'
tom._age
# 18
tom.__phone
# AttributeError: 'Person' object has no attribute '__phone'

tom.get_name()
# My name is Tom

tom._get_age()
# My age is 18
# My age is 12345678

tom.__get_phone()
# AttributeError: 'Person' object has no attribute '__get_phone'

继承 Inheritance

在不指定继承的父类时,所有类都继承object类(系统提供)。

  • 被其它类继承的类,称为父类,或者基类,或者超类。
  • 继承其它类的类,称为子类,或者派生类(derived class)。
  • 子类继承父类后,就拥有了父类中的所有成员(除了私有成员)。
  • 子类继承父类后,并不会把父类的成员复制给子类,而是引用。
  • 子类可以直接调用父类的方法super().BaseClassName。如果父类方法有参数要求,子类调用时也有参数要求。
  • 子类继承父类后,可以重新定义父类中的方法,称为**重写(Override)**。
  • 子类继承父类后,定义父类中没有的方法,被称为对父类的扩展。
  • 一个父类可以被多个子类继承。

**派生类(derived class)**定义的语法如下所示:

class BaseClassName():
    <statement-1>
    .
    .
    .
    <statement-N>

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

名称BaseClassName必须定义于包含派生类定义的作用域中。 也允许用其他任意表达式代替基类名称所在的位置,例如,当基类定义在另一个模块中的时候:

class DerivedClassName(modname.BaseClassName):

派生类定义的执行过程与基类相同。 当构造类对象时,基类会被记住。 此信息将被用来解析属性引用:如果请求的属性在类中找不到,搜索将转往基类中进行查找。 如果基类本身也派生自其他某个类,则此规则将被递归地(recursively)应用。

派生类的实例化没有任何特殊之处: DerivedClassName()会创建该类的一个*新实例*。 方法引用将按以下方式解析:搜索相应的类属性,如有必要将按基类继承链逐步向下查找,如果产生了一个函数对象则方法引用就生效。

派生类可能会重写(override)其基类的方法。 因为方法在调用同一对象的其他方法时没有特殊权限,所以调用同一基类中定义的另一方法的基类方法最终可能会调用覆盖它的派生类的方法。

在派生类中的重载方法(overriding method)实际上可能想要扩展而非简单地替换同名的基类方法。 有一种方式可以简单地直接调用基类方法:即调用BaseClassName.methodname(self, arguments)。 请注意,仅当此基类可在全局作用域中以BaseClassName的名称被访问时方可使用此方式。

Python有两个内置函数可被用于继承机制:

  • 使用isinstance()来检查一个实例的类型: isinstance(obj, int)仅会在obj.__class__int或某个派生自int的类时为True
  • 使用issubclass()来检查类的继承关系:issubclass(bool, int)True,因为boolint的子类。 但是,issubclass(float, int)False,因为float不是int的子类。

多重继承 Multiple Inheritance

单继承(single-inheritance):一个类只能继承一个父类方式。

class DerivedClassName(BaseClassName):
    <statement-1>
    .
    .
    .
    <statement-N>

多继承(Multiple Inheritance):一个类去继承多个类的方式。定义语句如下所示

class DerivedClassName(Base1, Base2, Base3):
    <statement-1>
    .
    .
    .
    <statement-N>

在最简单的情况下,搜索从父类所继承属性的操作是深度优先(depth-first)、从左至右(left-to-right)的,当层次结构中存在重叠时不会在同一个类中搜索两次。 因此,如果某一属性在DerivedClassName中未找到,则会到Base1中搜索它,然后(递归地)到Base1的基类中搜索,如果在那里未找到,再到Base2中搜索,依此类推。

真实情况更复杂;方法解析顺序会动态改变以支持对super()的协同调用。 这种方式在某些其他多重继承型语言中被称为**后续方法调用(call-next-method)**,它比**单继承(single-inheritance)**语言中的uper调用更强大。

动态改变顺序是有必要的,因为所有多重继承的情况都会显示出一个或更多的菱形关联(diamond relationships)(即至少有一个父类可通过多条路径被最底层类所访问)。 例如,所有类都是继承自object,因此任何多重继承的情况都提供了一条以上的路径可以通向 object。 为了确保基类不会被访问一次以上,动态算法会用一种特殊方式将搜索顺序线性化, 保留每个类所指定的从左至右的顺序,只调用每个父类一次,并且保持单调(monotonic)(即一个类可以被子类化而不影响其父类的优先顺序)。 总而言之,这些特性使得设计具有多重继承的可靠且可扩展的类成为可能。

看下面例子,定义了3个类和继承关系。

class F():

    def drink(self):
        print("Drink Beer")


class M():

    def drink(self):
        print("Drink Red Wine")


class C(F, M):

    def drink(self):
        print("Drink Water")

执行结果是

c = C()
c.drink()
# Drink Water

方法1:按照mro进行继承查找。 如果把C类改写为如下,可以调用父类,参照C类的mro进行,mro里面类F的上一级是类M,所以类F中的super()就是指类M

class C(F, M):

    def drink(self):
        super().drink()
        print("Drink Water")


c = C()
c.drink()
# Drink Beer
# Drink Water

C.mro()
# [<class '__main__.C'>, <class '__main__.F'>, <class '__main__.M'>, <class 'object'>]

方法2:“指名道姓”调用。如果把C类改写为如下,可以调用M类。

class C(F, M):

    def drink(self):
        M.drink(self)
        print("Drink Water")


c = C()
c.drink()
# Drink Red Wine
# Drink Water

菱形继承和继承关系检测

菱形继承的描述是,类A作为基类(这里基类是指非object类),类B和类C同时继承类A,然后类D又继承类B和类C,如下图,看起来像个钻石的形状。

    A
   / \
  B   C
   \ /
    D

在这种结构中,在调用顺序上就会出现疑惑,调用顺序究竟是以下哪一种顺序呢?

  • D->B->A->C(深度优先)
  • D->B->C->A(广度优先)

看下面代码,在Python3中,**菱形**的多继承关系是按照D->B->C->A**广度优先**的搜索方式。

class A():
    pass


class B(A):

    def test(self):
        print("init B.test()")


class C(A):

    def test(self):
        print("init C.test()")


class D(B, C):
    pass


d = D()
d.test()
# init B.test()


D.mro()
# [<class '__main__.D'>, <class '__main__.B'>, <class '__main__.C'>, <class '__main__.A'>, <class 'object'>]

对于下面这种**非菱形**的多继承关系,查找顺序是A->B->E->C->F->D**深度优先**的搜索方式。

E     F
|     |
B(E) C(F)  D
|     |    |
 \    |   /
  \   |  /
   A(B,C,D)

代码实现:

class D():

    def test(self):
        print("init D.test()")


class F():

    def test(self):
        print("init F.test()")


class C(F):
    pass


class E():
    pass


class B(E):
    pass


class A(B, C, D):
    pass


a = A()
a.test()
# init F.test()

A.mro()
# [<class '__main__.A'>, <class '__main__.B'>, <class '__main__.E'>, <class '__main__.C'>, <class '__main__.F'>, <class '__main__.D'>, <class 'object'>]

总结:

  1. 继承结构要尽量简单,不要过于复杂。
  2. 推荐使用minxins机制,在多继承背景下,满足继承的什么是什么的关系(is-a)

多继承关系的minxins机制

看下面例子,如果在Vehicle类中定义了fly的方法,会导致Car(Vehicle)的继承关系出现矛盾,汽车并不会飞,但按照上述继承关系,汽车也能飞了。 但是如果民航飞机和直升机都各自写自己的飞行fly方法,又违背了代码尽可能重用的原则。

class Vehicle:  # 交通工具

    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


# 民航飞机
class CivilAircraft(Vehicle):
    pass


# 直升飞机
class Helicopter(Vehicle):
    pass


# 汽车
class Car(Vehicle):
    pass

Python中没有类似Java接口interface的功能,但提供了Mixins机制。

  • Python对于Mixin类的命名方式一般以 Mixin, able, ible为后缀。
  • Mixin类必须功能单一,如果有多个功能,那就写多个Mixin类。
  • 一个类可以继承多个Mixin类,为了保证遵循继承的“is-a”原则,只能继承一个标识其归属含义的父类
  • Mixin类不依赖于子类的实现。
  • 子类即便没有继承这个Mixin类类,也照样可以工作,就是缺少了某个功能。

我们定义的Mixin类越多,子类的代码可读性就会越差。

# 交通工具
class Vehicle:
    pass


# 为当前类混入一些功能,不是一个单纯的类
class FlyableMixin:

    def fly(self):
        '''
        飞行功能相应的代码        
        '''
        print("I am flying")


# 民航飞机
class CivilAircraft(FlyableMixin, Vehicle):
    pass


# 直升飞机
class Helicopter(FlyableMixin, Vehicle):
    pass


# 汽车
class Car(Vehicle):
    pass

组合(Class Combination)

在一个类中以另一个类的对象作为数据属性,称为类的**组合**。组合与继承都是用来解决代码的重用性问题。 继承体现“是”的关系,当类之间有很多相同之处,用继承。 组合体现“有”的关系,当类之间有显著不同,一个类是另一个类的属性是,用组合。

下例是计算圆环的面积和周长,圆环是由两个圆组成的,圆环的面积是外面圆的面积减去内部圆的面积。圆环的周长是内部圆的周长加上外部圆的周长。

这个例子演示了类ring里面的属性circle1circle2正是另一个类Circle

from math import pi

class Circle():

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

    def area(self):
        return pi * self.r * self.r

    def perimeter(self):
        return 2 * pi * self.r


class Ring():

    def __init__(self, r1, r2):
        self.circle1 = Circle(r1)
        self.circle2 = Circle(r2)

    def area(self):
        return abs(self.circle1.area() - self.circle2.area())

    def permiter(self):
        return self.circle1.perimeter() + self.circle2.perimeter()


ring = Ring(5, 8)

print(ring.area())
# 122.52211349000193

print(ring.permiter())
# 81.68140899333463

下面的例子演示了如何通过传参的方式进行类的组合。

class Birthday():

    def __init__(self, year, month, day):
        self.year = year
        self.month = month
        self.day = day


class Course():

    def __init__(self, course_name, course_period):
        self.course_name = course_name
        self.course_period = course_period


class Professor():

    def __init__(self, name, gender, birth, course):
        self.name = name
        self.gender = gender
        self.birth = birth
        self.course = course

    def teach(self):
        print(f"Professor name: {self.name}; Gender: {self.gender}; Birthday: {self.birth.year}-{self.birth.month}{self.birth.day}, Course name: {self.course.course_name} and period: {self.course.course_period}")


prof = Professor('Tom', 'Male', Birthday(1985, 5, 5), Course('Chinese', '2022/3/1 ~ 2022/6/30'))

prof.teach()
# Professor name: Tom; Gender: Male; Birthday: 1985-55, Course name: Chinese and period: 2022/3/1 ~ 2022/6/30

多态 Polymorphism

多态意味着相同的函数名用于不同的情形。 如下例,len()被用于不同的情形。

# len() being used for a string
print(len("geeks"))
# 5

# len() being used for a list
print(len([10, 20, 30]))
# 3

类方法的多态性

下面的代码展示了 Python 如何以相同的方式使用两种不同的类类型。 我们创建了一个遍历对象元组的 for 循环。 然后调用方法而不用关心每个对象是哪个类类型。 我们假设这些方法实际上存在于每个类中。

class India():

    def capital(self):
        print("New Delhi is the capital of India.")

    def language(self):
        print("Hindi is the most widely spoken language of India.")

    def type(self):
        print("India is a developing country.")


class USA():

    def capital(self):
        print("Washington, D.C. is the capital of USA.")

    def language(self):
        print("English is the primary language of USA.")

    def type(self):
        print("USA is a developed country.")


obj_ind = India()
obj_usa = USA()

for country in (obj_ind, obj_usa):
    country.capital()
    country.language()
    country.type()

# New Delhi is the capital of India.
# Hindi is the most widely spoken language of India.
# India is a developing country.
# Washington, D.C. is the capital of USA.
# English is the primary language of USA.
# USA is a developed country.

继承的多态性

在 Python 中,多态允许我们在子类中定义与父类中的方法同名的方法。 在继承中,子类继承父类的方法。 但是,可以修改从父类继承的子类中的方法。 这在从父类继承的方法不太适合子类的情况下特别有用。 在这种情况下,我们在子类中重新实现该方法。 这种在子类中重新实现方法的过程称为**方法覆盖(Method Overriding)**。

class Bird:

    def intro(self):
        print("There are many types of birds.")

    def flight(self):
        print("Most of the birds can fly but some cannot.")


class sparrow(Bird):

    def flight(self):
        print("Sparrows can fly.")


class ostrich(Bird):

    def flight(self):
        print("Ostriches cannot fly.")


obj_bird = Bird()
obj_spr = sparrow()
obj_ost = ostrich()

obj_bird.intro()
# There are many types of birds.

obj_bird.flight()
# Most of the birds can fly but some cannot.

obj_spr.intro()
# There are many types of birds.

obj_spr.flight()
# Sparrows can fly.

obj_ost.intro()
# There are many types of birds.

obj_ost.flight()
# Ostriches cannot fly.

函数和对象的多态性

我们也可以创建一个可以接受任何对象的函数,允许多态性。 在下面例子中,我们创建一个名为func()的函数,传入参数是obj的对象。 在这种情况下,我们调用三个方法,即capital()language()type(),每个方法都定义在IndiaUSA两个类中。 我们可以使用相同的 func() 函数调用它们的动作:

class India():

    def capital(self):
        print("New Delhi is the capital of India.")

    def language(self):
        print("Hindi is the most widely spoken language of India.")

    def type(self):
        print("India is a developing country.")


class USA():

    def capital(self):
        print("Washington, D.C. is the capital of USA.")

    def language(self):
        print("English is the primary language of USA.")

    def type(self):
        print("USA is a developed country.")


def func(obj):
    obj.capital()
    obj.language()
    obj.type()


obj_ind = India()
obj_usa = USA()

func(obj_ind)
# New Delhi is the capital of India.
# Hindi is the most widely spoken language of India.
# India is a developing country.

func(obj_usa)
# Washington, D.C. is the capital of USA.
# English is the primary language of USA.
# USA is a developed country.

鸭子类型(Ducking Typing)和白鹅类型(Goose Typing)

在Python中实现多态主要有两种机制:白鹅类型和鸭子类型。白鹅类型和鸭子类型不仅是两种机制,也是两种不同的编程风格。

下面是一个打印商品价格的例子,分别用鸭子类型和白鹅类型实现。

鸭子类型。

在鸭子类型的实现中,我们只需要保证调用price方法的每个对象都有price方法即可。

class Food:
    def price(self):
       print("{} price:$4".format(__class__.__name__))

class Clothes:
    def price(self):
       print("{} price:$5".format(__class__.__name__))

class Coffee:
    def price(self):
       print("{} price:$6".format(__class__.__name__))


if __name__ == '__main__':

    goods = [Food(), Clothes(), Coffee()]

    for good in goods:
       good.price()

# Food price:$4
# Clothes price:$5
# Coffee price:$6

白鹅类型。

在白鹅类型中,直接让所有对象的类继承父类Good中的抽象方法price。Python中的白鹅类型机制就是强类型语言中实现多态的标准模式,即通过调取父类的虚函数或者继承的函数来完成不同的行为。

import abc

class Good(abc.ABC):
    @abc.abstractmethod
    def price(self):
        pass

class Food(Good):
    def price(self):
        print("{} price:$4".format(__class__.__name__))

class Clothes(Good):
    def price(self):
        print("{} price:$5".format(__class__.__name__))


if __name__ == '__main__':

    goods = [Food(), Clothes(), Coffee()]

    for good in goods:
        good.price()

# Food price:$4
# Clothes price:$5
# Coffee price:$6

类方法(Class method)和静态方法(Static Method)

类方法(Class method)也叫绑定方法,必须把类作为传入参数,使用cls作为第一个传入参数,而静态方法(Static Method),也叫非绑定方法,不需要特定的参数。

类方法是绑定到类的,不是绑定到类对象,所以类方法可以访问或修改类,并对所有类实例生效。

静态方法无法直接访问或修改类,因为静态方法是不知道类本身的,静态方法是属于工具类方法,基于传入的参数完成特定的功能,其实就是一个普通函数而已。

Python中使用@classmethod装饰器(decorator)来创建一个类方法,用@staticmethod装饰器来创建一个静态方法。

语法格式:

@classmethod
def fun(cls, arg1, arg2, ...):

其中:

fun: 需要转换成类方法的函数
returns: 函数的类方法

classmethod()方法绑定到类而不是对象。类方法可以被类和对象调用。这些方法可以通过类或对象进行调用。

例1:创建一个简单的classmethod。 创建一个类Training,有类变量course和方法purchase。 我们通过把函数Training.purchase传给classmethod(),把该方法转成类方法,然后直接调用它,而无需先创建对象。 可以看出转换前后Training.purchase的类型变化。

class Training:
 course = 'Python for Data Analysis'

 def purchase(obj):
  print("Puchase course : ", obj.course)


type(Training.purchase)
# <class 'function'>

Training.purchase = classmethod(Training.purchase)

Training.purchase()
# Puchase course :  Python for Data Analysis

type(Training.purchase)
# <class 'method'>

例2:使用装饰器@classmethod创建工厂类。

class Training:
    def __init__(self, course):
        self.course = course

    @classmethod
    def purchase(cls, course):
        return cls(course)

    def display(self):
        print('Purchase course: ', self.course)


training = Training("Python for Data Analysis")
training.display()
# Purchase course:  Python for Data Analysis

例3:通过staticmethod()classmethod()来检查一个person是否是adult。 person1是通过姓名和年龄创建的实例。person2是通过姓名和年份创建的实例。

from datetime import date


class Person:

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

    @classmethod
    def fromBirthYear(cls, name, year):
        return cls(name, date.today().year - year)

    @staticmethod
    def isAdult(age):
        return age > 18


person1 = Person('mayank', 21)
person2 = Person.fromBirthYear('mayank', 1996)

print(person1.age)
# 21
print(person2.age)
# 26
print(Person.isAdult(22))
# True

小结: 若类中需要一个功能,该功能的实现代码中需要引用对象,则将其定义成对象方法;需要引用类,则将其定义成类方法;无需引用类或对象,则将其定义成静态方法。

猴子补丁(monkey patch)

猴子补丁是动态为已经创建出的对象增加新的方法和属性成员的一种机制,也就是动态打补丁。

实例化对象的猴子补丁。

class Test:
    def __init__(self):
       self.a = 1

    def func1(self, x, y):
       print(x + y)


# 正常实例化
test = Test()
test.func1(1, 1)
# 2

# 修改实例
test.func1 = lambda x, y : print(x + 2 * y)
test.func1(1, 1)
# 3

# 通过修改实例,访问内部成员变量。
test.func1 = lambda x, y : print(x + 2 * y + self.a)
test.func1(1, 1)
# NameError: name 'self' is not defined
test.func1 = lambda self, x, y : print(x + 2 * y + self.a)
test.func1(test, 1, 1)
# 4

类对象的猴子补丁。

class Test:
    def __init__(self):
       self.a = 1

    def func1(self, x, y):
       print(x + y)


# 修改类成员,实例化后的结果已修改。
Test.func1 = lambda self, x, y : print(x + 2 * y)

test = Test()
test.func1(1, 1)
# 3


# 修改类成员,并访问成员变量,实例化后的结果已修改。
Test.func1 = lambda self, x, y : print(x + 2 * y + self.a)

test = Test()
test.func1(1, 1)
# 4

# 增加类成员。
Test.func2 = lambda self, p, q: print(p + 3 * q + self.a)
test = Test()
test.func1(1, 1)
# 4
test.func2(1, 3)
# 11

私有变量 Private Variables

那种仅限从一个对象内部访问的“私有”实例变量(“Private” instance variables)在 Python 中并不存在。 但是,大多数 Python 代码都遵循这样一个约定:带有*一个前缀下划线*的名称 (例如_spam) 应该被当作是 API 的非公有(non-public)部分 (无论它是函数、方法或是数据成员)。 这应当被视为一个实现细节,可能不经通知即加以改变。

由于存在对于类私有成员(class-private members)的有效使用场景(例如避免名称与子类所定义的名称相冲突),因此存在对此种机制的有限支持,称为**名称改写(name mangling)**。 任何形式为__spam的标识符(至少带有*两个前缀下划线*,至多一个后缀下划线)的文本将被替换为 _classname__spam,其中 classname 为去除了前缀下划线的当前类名称。 这种改写不考虑标识符的句法位置,只要它出现在类定义内部就会进行。

名称改写(Name mangling)有助于让子类重载方法()override methods而不破坏类内方法(intraclass method)调用。例如:

class Mapping:

    def __init__(self, iterable):
        self.items_list = []
        self.__update(iterable)

    def update(self, iterable):
        for item in iterable:
            self.items_list.append(item)

    __update = update  # private copy of original update() method


class MappingSubclass(Mapping):

    def update(self, keys, values):
        # provides new signature for update()
        # but does not break __init__()
        for item in zip(keys, values):
            self.items_list.append(item)

上面的示例即使在 MappingSubclass 引入了一个 __update 标识符的情况下也不会出错,因为它会在 Mapping 类中被替换为 _Mapping__update 而在 MappingSubclass 类中被替换为 _MappingSubclass__update

请注意,改写规则(mangling rules)的设计主要是为了避免意外冲突;访问或修改私有变量仍然是可能的。这在特殊情况下甚至会很有用,例如在调试器(debugger)中。

请注意传递给 exec()eval() 的代码不会把发起调用类的类名视作当前类;这类似于 global 语句的效果,因此这种效果仅限于同时经过字节码编译的代码。 同样的限制也适用于 getattr(), setattr()delattr(),以及对于 __dict__ 的直接引用。

反射(reflection)

反射(reflection)是动态语言的一个特性。**反射机制**指的是在程序的运行状态中,对于任意一个类,都可以知道这个类的所有属性和方法;对于任意一个对象,都能够调用他的任意方法和属性。这种动态获取程序信息以及动态调用对象的功能称为反射机制。

通过下面例子可知,通过dir(person)获取任意一个类或者对象的属性列表。通过内置函数hasattrgetattrsetattrdelattr操作类和对象。

class Person:

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


person = Person('Tom', 21, 'Male')

print(dir(person))
# ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', 'age', 'gender', 'name']

# hasattr(object,'name')
# 按字符串'name'判断有无属性person.name
hasattr(person, 'name')
# True

# getattr(object, 'name', default=None)
# 等同于person.name,不存在该属性则返回默认值None
getattr(person, 'name', None)
# 'Tom'

# setattr(x, 'y', v)
# 等同于person.age = 18
setattr(person, 'age', 18)
print(person.age)
# 18

# delattr(x, 'y')
# 等同于del person.age
delattr(person, 'age')
print(person.age)
# AttributeError: 'Person' object has no attribute 'age'

下面是一个实际应用的例子。

class FtpServer():

    def server_run(self):
        while True:
            inp = input('Input your command >>:').strip()
            cmd, file = inp.split()

            if hasattr(self, cmd):
                func = getattr(self, cmd)
                func(file)

    def get(self, file):
        print(f'Downloading {file}...')

    def put(self, file):
        print(f'Uploading  {file}...')


ftp_server = FtpServer()

ftp_server.server_run()
# Input your command >>:get a.ext
# Downloading a.ext...
# Input your command >>:put a.txt
# Uploading  a.txt...

迭代器 Iterators

在Python中,大多数容器对象(container object)都可以使用 for 语句:

for element in [1, 2, 3]:
    print(element)
for element in (1, 2, 3):
    print(element)
for key in {'one': 1, 'two': 2}:
    print(key)
for char in "123":
    print(char)
for line in open("myfile.txt"):
    print(line, end='')

for 语句会在容器对象上调用 iter()。 该函数返回一个定义了 __next__() 方法的迭代器对象,此方法将逐一访问容器中的元素。 当元素用尽时,__next__() 将引发 StopIteration 异常来通知终止 for 循环。 可以使用 next() 内置函数来调用 __next__() 方法;下面这个例子展示了刚刚描述的具体运行方式:

>>> s = 'abc'
>>> it = iter(s)
>>> it
<str_iterator object at 0x10c90e650>
>>> next(it)
'a'
>>> next(it)
'b'
>>> next(it)
'c'
>>> next(it)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
    next(it)
StopIteration

在了解了迭代器协议(iterator protocol)的机制后,给类添加迭代器就很容易了。 定义一个 __iter__() 方法来返回一个带有 __next__() 方法的对象。 如果类已定义了 __next__(),则 __iter__() 可以简单地返回 self:

class Reverse:
    """Iterator for looping over a sequence backwards."""

    def __init__(self, data):
        self.data = data
        self.index = len(data)

    def __iter__(self):
        return self

    def __next__(self):
        if self.index == 0:
            raise StopIteration
        self.index = self.index - 1
        return self.data[self.index]


rev = Reverse('spam')

print(iter(rev))

for char in rev:
    print(char)

# m
# a
# p
# s

生成器 Generators

**生成器(Generators)**是一个用于创建迭代器的简单而强大的工具。 它们的写法类似于标准的函数,但当它们要返回数据时会使用 yield 语句。 每次在生成器上调用 next() 时,它会从上次离开的位置恢复执行(它会记住上次执行语句时的所有数据值)。

一个创建生成器的示例如下(改写上面迭代器中所举的例子):

def reverse(data):
    for index in range(len(data) - 1, -1, -1):
        yield data[index]


for char in reverse('golf'):
    print(char)

# f
# l
# o
# g

可以用生成器来完成的操作同样可以用前面所描述的基于类的迭代器来完成。但生成器的写法更为紧凑,因为它会自动创建 __iter__()__next__() 方法。

另一个关键特性在于局部变量和执行状态会在每次调用之间自动保存。 这使得该函数相比使用 self.indexself.data 这种实例变量的方式更易编写且更为清晰。

除了会自动创建方法和保存程序状态,当生成器终结时,它们还会自动引发 StopIteration

生成器表达式 Generator Expressions

某些简单的生成器可以写成简洁的表达式代码,所用语法类似列表推导式,但外层为圆括号而非方括号。 这种表达式被设计用于生成器将立即被外层函数所使用的情况。 生成器表达式相比完整的生成器更紧凑但较不灵活,相比等效的列表推导式则更为节省内存。

示例:

>>> sum(i * i for i in range(10))  # sum of squares
285

>>> xvec = [10, 20, 30]
>>> yvec = [7, 5, 3]
>>> sum(x * y for x, y in zip(xvec, yvec))  # dot product
260

>>> unique_words = set(word for line in page  for word in line.split())

>>> valedictorian = max((student.gpa, student.name) for student in graduates)

>>> data = 'golf'
>>> list(data[i] for i in range(len(data)-1, -1, -1))
['f', 'l', 'o', 'g']

元类(metaclass)

所有的对象都是实例化或者说调用类而得到的(调用类的过程称为类的实例化。

class StandfordProfessor(object):
    university = 'Standford'

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

    def display(self):
        print(f'Professor {self.name} says welcome to {self.university}!')

professor = StandfordProfessor('Tom', 'Male')

上例中,对象professor是调用类StandfordProfessor得到的。类StandfordProfessor本质也是一个对象, 下面可以验证,StandfordProfessor是调用了内置的类type得到的。这个type称为元类。

print(type(StandfordProfessor))
# <class 'type'>

如果一个类没有声明自己的元类,默认它的元类就是type,除了使用内置元类type,我们也可以通过继承type来自定义元类,然后使用metaclass关键字参数为一个类的指定元类。

只有继承了type类才能称之为一个元类,否则就是一个普通的自定义类。

class Mymeta(type):
    pass


class StandfordProfessor(object, metaclass=Mymeta):
    university = 'Standford'

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

    def display(self):
        print(f'Professor {self.name} says welcome to {self.university}!')


professor = StandfordProfessor('Tom', 'Male')

下面进行自定义元类,控制类StandfordProfessor的调用。

要想让professor这个对象变成一个可调用的对象,需要在该对象的类中定义一个方法__call__,该方法会在调用对象时自动触发。调用professor的返回值就是__call__方法的返回值。

class Mymeta(type):

    def __call__(self, *args, **kwargs):
        print(self)  # 类名
        print(args)  # 输入参数
        print(kwargs)  # 输入参数
        return 10086


class StandfordProfessor(object, metaclass=Mymeta):
    university = 'Standford'

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

    def display(self):
        print(f'Professor {self.name} says welcome to {self.university}!')


professor = StandfordProfessor('Tom', 'Male')
# <class '__main__.StandfordProfessor'>
# ('Tom', 'Male')
# {}

类的产生过程其实就是元类的调用过程,即StandfordProfessor = Mymeta('StandfordProfessor', (object), {...}),调用Mymeta会先产生一个空对象StandfordProfessor,然后连同调用Mymeta括号内的参数一同传给Mymeta下的__init__方法,完成初始化。我们可以基于上例做如下改写。

class Mymeta(type):

    def __init__(self, class_name, class_bases, class_dic):
        super(Mymeta, self).__init__(class_name, class_bases, class_dic)

        if class_name.islower():
            raise TypeError(f'Please follow Camel-Case to change class name {class_name}')
        if '__doc__' not in class_dic or len(class_dic['__doc__'].strip(' \n')) == 0:
            raise TypeError('Please add documentation in class {class_name}, which is mandatory.')


class StandfordProfessor(object, metaclass=Mymeta):
    """
    Documentation of class StanfordTeacher
    """

    university = 'Standford'

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

    def display(self):
        print(f'Professor {self.name} says welcome to {self.university}!')


professor = StandfordProfessor('Tom', 'Male')

professor.display()
# Professor Tom says welcome to Standford!

print(professor.__dict__)
# {'name': 'Tom', 'gender': 'Male'}

StandfordProfessor.mro()
# [<class '__main__.StandfordProfessor'>, <class 'object'>]