<jupyter_start><jupyter_text>python 中的特别语法 本节主要对python中一些令人印象深刻的基础语法进行归纳总结，为更高级的python语句的理解做铺垫。"\*"和"\_"等特殊符号在python语法中十分常见，本节先从这些符号开始说起。然后记录一些 magic function；最后补充一些特别情况说明。 _号的用法本小节主要参考：[Python中下划线的5种含义](https://zhuanlan.zhihu.com/p/36173202)。下划线在python变量、方法等的名称中都各有其含义，主要包括两个层面：- 约定的含义，对程序员的提示；- 由Python解释器严格执行的。主要讨论5类下划线模式及对python程序的行为的影响：- 单前导下划线（_var）：一个命名约定，表示变量或方法仅内部（比如类内，模块内等）使用；- 单末尾下划线（var_）：一个命名约定，用来避免与关键字产生命名冲突；- 双前导下划线（__var）：解释器会更改该变量的名称，以便在类被扩展的时候不冲突；- 双前导和末尾下划线（__var__）：Python保留了这类名称用于特殊用途，最好避免在自己的程序中使用；- 单下划线（_）：约定，表示这个变量是临时的，也是一个特殊变量，解释器评估的最近一个表达式的结果。综上，单下划线的基本上属于约定类的，双下划线的则是解释器级的。下面给例子说明下__var和__var__<jupyter_code>class Test:
   def __init__(self):
       self.foo = 11
       self._bar = 23
       self.__baz = 23
        
t = Test()
dir(t)<jupyter_output><empty_output><jupyter_text>dir()函数可查看对象的属性，从上例中可以看出，__baz变量已经变为_Test__baz变量。也就是说双下划线变量的这种名称修饰对程序员是透明的，这是为了防止被意外修改。对方法的命名也是一样的，这里就不再赘述了。首尾双下划线的例子，以__call__函数为例。<jupyter_code># __call__函数的作用
# /usr/bin/env python
class test:
    def __init__(self, a):
        self.a = a

    def __call__(self, b):
        c = self.a + b
        print(c)

    def display(self):
        print(self.a)


Test = test("This is test!")
Test.display()  # 调用display函数
Test("##Append something")  # __call__实际上是将一个类重载了"()"，也就是让一个类也可以像一个函数一样可以拿来调用了。因此这里会调用__call__函数<jupyter_output>This is test!
This is test!##Append something<jupyter_text>*的用法这部分主要参考：[Python函数中*和**的内涵究竟是什么呢？](https://www.zhihu.com/question/265519629)。星号主要在函数定义和函数调用的时候使用：- 函数定义：  - 使用单个星号会将所有的参数放入一个元组tuple供函数使用；  - 使用两个星号会将所有的参数放入一个字典dict供函数使用；- 函数调用：  - 在list、tuple、set前加一个星号会把容器中所有元素解包变成位置参数；  - 在dict前加一个星号会把字典的键变成位置参数；  - 在dict前加两个星号会把字典的键值对变成关键字参数。  结合以下例子便可理解。<jupyter_code>def foo(*args):
    for a in args:
        print(a)
        
foo(1)
print()
foo(1,2,3)
def bar(**kwargs):
    for a in kwargs:
        print(a, kwargs[a])
        
bar(name='one', age=27)
def foo(x,y,z):
    print("x=" , x)
    print("y=" , y)
    print("z=" , z)
    
mylist = [1,2,3]
foo(*mylist)
mydict = {'x':1,'y':2,'z':3}
foo(**mydict)
def foo(param1, *param2):
    print(param1)
    print(param2)

def bar(param1, **param2):
    print(param1)
    print(param2)

foo(1,2,3,4,5)
bar(1,a=2,b=3)<jupyter_output>1
(2, 3, 4, 5)
1
{'a': 2, 'b': 3}<jupyter_text>@的用法这里涉及到python装饰器的一些内容。主要参考了：[如何理解Python装饰器](https://www.zhihu.com/question/26930016)和[Python装饰器和符号@](https://gohom.win/2015/10/25/pyDecorator/)装饰器本质上是一个**Python函数**，它可以让其他函数在**不需要做任何代码变动**的前提下**增加额外功能**，装饰器的**返回值**也是一个**函数对象**，它经常用于有切面需求的场景（类似java里的注解），比如插入日志、性能测试等。<jupyter_code>def log(func):
    def wrapper(*args, **kw):
        print('call %s():' % func.__name__)
        return func(*args, **kw)
    return wrapper

@log
def now():
    print('2015-10-26')
    return "done"
now()<jupyter_output>call now():
2015-10-26<jupyter_text>机制就是,调用now()实际调用log(now)() (前面@写法后,实际运行now=log(now)),也就是运行了wrapper(),并把now函数原有参数传递给了wrapper函数. wrapper在运行时,加入了新的处理print 'call %s():' % func.__name__一句, 并运行相应传递参数的func(*args,**kw)并把原有结果返回.带参数的装饰器会稍微复杂一些，它是对原有装饰器的一个函数封装，并返回一个装饰器。<jupyter_code>def log(text):
    def decorator(func):
        def wrapper(*args, **kw):
            print('%s %s():' % (text, func.__name__))
            return func(*args, **kw)
        return wrapper
    return decorator
@log('execute')
def now():
    print('2015-10-26')
    return "done"
now()<jupyter_output>execute now():
2015-10-26<jupyter_text>实际now=log('execute')(now)两个参数表就是执行了一次闭包decorator(now).执行该闭包后返回的才是真正的装饰器wrapper.两层闭包的机制可以保证传递参数给内在的装饰器wrapper.第一层将参数传进行生成第一层闭包对应返回函数,第二层则将该参数继续留给真正的装饰器闭包。此外，还有类装饰器，类装饰器有灵活度大、高内聚、封装性等优点，使用类装饰器要依靠类内部的__call__方法。<jupyter_code>class Foo(object):
    def __init__(self, func):
        self._func = func

    def __call__(self):
        print ('class decorator runing')
        self._func()
        print ('class decorator ending')

@Foo
def bar():
    print ('bar')

bar()<jupyter_output>class decorator runing
bar
class decorator ending<jupyter_text>使用装饰器会丢失一些原函数的元信息，这时候可以用functools.wraps，它把原函数的元信息拷贝到装饰器函数中。此外，还有内置装饰器@staticmathod、@classmethod、@property等。先补充下对staticmathod、classmethod的介绍。- classmethod : 被classmethod()函数处理过的函数,能被类所调用，也能被对象所调用（是继承的关系）。- staticmethod: 相当于“全局函数”，可以被类直接调用，可以被所有实例化对象共享，通过staticmethod()定义静态方法，静态方法没有self参数关于两者的解释可以参考：[Python 中的 classmethod 和 staticmethod 有什么具体用途？](https://www.zhihu.com/question/20021164)。Python中的类也是一个普通对象，如果需要**直接使用这个类**，例如将类作为参数传递到其他函数中，又希望在实例化这个类之前就能提供某些功能，那么最简单的办法就是使用classmethod和staticmethod。这两者的区别在于在存在类的继承的情况下**对多态的支持不同**。本质上来说，面向对象中实例方法有哪些作用，classmethod也就有哪些作用，只是这个面向的 **“对象”是类本身**而已。这和C++中的static method其实是不同的，C++中的static method只有命名空间的作用，而Python中不管是classmethod还是staticmethod都有OOP、多态上的意义。首先，看看共同点的使用条件。举例分析，类中最常用的方法是实例方法, 即通过通过实例作为第一个参数的方法。比如，一个基本的实例方法就向下面这个:<jupyter_code>class Kls(object):
    def __init__(self, data):
        self.data = data
    def printd(self):
        print(self.data)
ik1 = Kls('arun')
ik2 = Kls('seema')
ik1.printd()
ik2.printd()<jupyter_output>arun
seema<jupyter_text>如果现在我们想写一些**仅仅与类交互而不是和实例交互的方法**会怎么样呢? 我们可以在类外面写一个简单的方法来做这些，但是这样做就扩散了类代码的关系到类定义的外面. 如果像下面这样写就会导致以后代码维护的困难:<jupyter_code>def get_no_of_instances(cls_obj):
    return cls_obj.no_inst
class Kls(object):
    no_inst = 0
    def __init__(self):
        Kls.no_inst = Kls.no_inst + 1
ik1 = Kls()
ik2 = Kls()
print(get_no_of_instances(Kls))<jupyter_output>2<jupyter_text>我们要写一个只在类中运行而不在实例中运行的方法.可以使用：<jupyter_code>def iget_no_of_instance(ins_obj):
    return ins_obj.__class__.no_inst
class Kls(object):
    no_inst = 0
    def __init__(self):
        Kls.no_inst = Kls.no_inst + 1
ik1 = Kls()
ik2 = Kls()
print (iget_no_of_instance(ik1))<jupyter_output>2<jupyter_text>现在可以使用@classmethod装饰器来创建类方法.<jupyter_code>class Kls(object):
    no_inst = 0
    def __init__(self):
        Kls.no_inst = Kls.no_inst + 1
    @classmethod
    def get_no_of_instance(cls_obj):
        return cls_obj.no_inst
ik1 = Kls()
ik2 = Kls()
print (ik1.get_no_of_instance())
print (Kls.get_no_of_instance())<jupyter_output>2
2<jupyter_text>这样的好处是: 不管这个方式是从实例调用还是从类调用，它都用第一个参数把类传递过来.另外，经常有一些**跟类有关系的功能**但**在运行时又不需要实例和类参与**的情况下需要用到**静态方法**. 比如更改环境变量或者修改其他类的属性等能用到静态方法. 这种情况可以直接用函数解决, 但这样同样会扩散类内部的代码，造成维护困难. 比如有一个时间类。<jupyter_code>class Data_test(object):
    day=0
    month=0
    year=0
    def __init__(self,year=0,month=0,day=0):
        self.day=day
        self.month=month
        self.year=year

    def out_date(self):
        print ("year :",self.year)
        print( "month :",self.month)
        print ("day :",self.day)
        
t=Data_test(2016,8,1)
t.out_date()<jupyter_output>year : 2016
month : 8
day : 1<jupyter_text>如果用户输入的是 "2016-8-1" 这样的字符格式，那么就需要调用Date_test 类前做一下处理：<jupyter_code>string_date='2016-8-1'
year,month,day=map(int,string_date.split('-'))
s=Data_test(year,month,day)
s.out_date()<jupyter_output>year : 2016
month : 8
day : 1<jupyter_text>那可不可以把这个字符串处理的函数放到 Date_test 类当中呢？这时候就可以用到@classmethod，在Date_test类里面创建一个成员函数， 前面用@classmethod装饰。它可以传进来一个当前类作为第一个参数。<jupyter_code>class Data_test2(object):
    day=0
    month=0
    year=0
    def __init__(self,year=0,month=0,day=0):
        self.day=day
        self.month=month
        self.year=year

    @classmethod
    def get_date(cls,string_date):
        #这里第一个参数是cls， 表示调用当前的类名
        year,month,day=map(int,string_date.split('-'))
        date1=cls(year,month,day)
        #返回的是一个初始化后的类
        return date1

    def out_date(self):
        print ("year :",self.year)
        print( "month :",self.month)
        print ("day :",self.day)<jupyter_output><empty_output><jupyter_text>可以使用如下方法调用：<jupyter_code>r=Data_test2.get_date("2016-8-6")
r.out_date()<jupyter_output>year : 2016
month : 8
day : 6<jupyter_text>这样子等于先调用get_date（）对字符串进行处理，然后才使用Data_test的构造函数初始化。这样的好处就是你以后**重构类的时候不必要修改构造函数，只需要额外添加你要处理的函数**，然后使用装饰符 @classmethod 就可以了。以上参考：[python @classmethod 的使用场合](http://30daydo.com/article/89)此外，通过类方法类内的方法 ，不涉及的属性和方法不会被加载，节省内存，快。接着看看两者的区别，首先看看两者的构造方式：<jupyter_code>class People(object):
    color = 'yellow'
    __age = 30   #私有属性

    def think(self):
        self.color = "black"
        print ("I am a %s "  % self.color)
        print ("I am a thinker")
        print (self.__age)

    def  __talk(self):
        print ("I am talking with Tom")

    def test(self):
        print ('Testing....')

    cm = classmethod(test)

jack = People()
People.cm()
class People(object):
    color = 'yellow'
    __age = 30   #私有属性

    def think(self):
        self.color = "black"
        print ("I am a %s "  % self.color)
        print ("I am a thinker")
        print (self.__age)

    def  __talk(self):
        print( "I am talking with Tom")

    def test():   ##没有self   静态调用     会把所有的属性加载到内存里。
        print (People.__age  ) #  通过类访问内部变量

    sm = staticmethod(test)

jack = People()
People.sm()
class People(object):
    color = 'yellow'
    __age = 30   #私有属性

    def think(self):
        self.color = "black"
        print ("I am a %s "  % self.color)
        print ("I am a thinker")
        print (self.__age)

    def  __talk(self):
        print ("I am talking with Tom")

    @classmethod #调用类的方法 
    def test(self):
        print ("this is class method")

    @staticmethod  #调用类的方法 
    def test1():    
        print ("this is static method")

jack = People()
People.test()
People.test1()<jupyter_output>this is class method
this is static method<jupyter_text>classmethod与staticmethod这两个方法的用法是类似的，大多数情况下，classmethod也可以通过staticmethod代替，在通过类调用时，这两者对于调用者来说是不可区分的。两者的区别在于，classmethod增加了一个对实际调用类的引用，这带来了很多方便的地方：方法可以判断出自己是通过基类被调用，还是通过某个子类被调用；通过子类调用时，方法可以返回子类的实例而非基类的实例；通过子类调用时，方法可以调用子类的其他classmethod.一般来说classmethod可以完全替代staticmethod。staticmethod唯一的好处是调用时它**返回的是一个真正的函数**，而且**每次调用时返回同一个实例**（classmethod则会对基类和子类返回不同的bound method实例），但这点几乎从来没有什么时候是有用的。Python中的classmethod（和staticmethod）并不止拥有美学上（或者命名空间上）的意义，而是可以实际参与多态的、足够纯粹的OOP功能，原理在于Python中类可以作为first class的对象使用，很大程度上替代其他OOP语言中的**工厂模式**。classmethod既可以作为factory method提供额外的构造实例的手段，也可以作为工厂类的接口，用来读取或者修改工厂类本身。classmethod还可以通过额外的类引用，提供继承时的多态特性，实现子类挂载点等。这段话参考了：https://www.zhihu.com/question/20021164/answer/537385841一个图解，参考：[Difference between @staticmethod and @classmethod in Python](https://www.pythoncentral.io/difference-between-staticmethod-and-classmethod-in-python/)：![](pictures/comparison.png)对应代码：<jupyter_code>class Kls(object):
    def __init__(self, data):
        self.data = data
 
    def printd(self):
        print(self.data)
 
    @staticmethod
    def smethod(*arg):
        print('Static:', arg)
 
    @classmethod
    def cmethod(*arg):
        print('Class:', arg)
ik = Kls(23)
ik.printd()
ik.smethod()
ik.cmethod()
Kls.printd()  # 类是不能直接调用对象方法的，会报错
Kls.smethod()
Kls.cmethod()<jupyter_output>Static: ()
Class: (<class '__main__.Kls'>,)<jupyter_text>再看看@property。该装饰器就像java，c++里面将变量声明为private一样，负责把一个方法变成属性调用的。这样还可以设置一些检查，防止犯错。<jupyter_code>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<jupyter_output><empty_output><jupyter_text>@score.setter相当于设置了set函数。<jupyter_code>s = Student()
s.score = 60
s.score
s.score = 9999<jupyter_output><empty_output><jupyter_text>其他符号- "."符号除了取值时用，在from . import XXX中表示sessions当前文件夹里的初始化文件，也就是sessions所在目录下的__init__.py文件；from .. import XXX就是从上级目录里面的__init__.py文件导入XXX。- "%"符号除了取余运算外，在字符串的格式化操作中起到占位符的作用。<jupyter_code>print("%6.3f" % 2.3) # 6为显示宽度，3为小数点位数，f为浮点数类型，第二个"%"后面为显示的内容来源，输出结果右对齐，2.300长度为5，故前面有一空格
print("%+10x" % 10) # x为表示16进制，显示宽度为10，前面有8个空格
print("%-5x" % -10) #  "%-5x" 负号为左对齐，显示宽度为5，故-a后面有3个空格

pi=3.1415
print ("pi的值是%s"%pi)
print ("pi的值是%.8f"%pi)

print("%10.*f" % (4, 1.2))<jupyter_output>2.300
        +a
-a   
pi的值是3.1415
pi的值是3.14150000
    1.2000<jupyter_text>类中特殊函数本节记录一些类中特殊函数的作用，主要参考了：[Enriching Your Python Classes With Dunder (Magic, Special) Methods](https://dbader.org/blog/python-dunder-methods:~:text=In%20Python%2C%20special%20methods%20are,__%20or%20__str__%20.)在Python中，特殊方法是可以用来丰富类的一组预定义方法。它们很容易识别，因为它们以双下划线开头和结尾，例如__init__或__str__。因为说“下划线下划线方法下划线下划线”比较麻烦，所以 Python者 就采用了“Dunder”一词，这是“双重下划线”的简称。Python中的这些“dunders”也称为“magic 方法”。但是使用这种术语可能会使它们看起来比实际复杂得多—因为它们并没有什么“神奇”的东西，应该将这些方法视为正常的语言功能。Dunder方法使您可以模拟内置类型的行为。例如，要获取字符串的长度，可以调用len('string')。但是，空类定义不支持以下行为：<jupyter_code>class NoLenSupport:
    pass

obj = NoLenSupport()
len(obj)<jupyter_output><empty_output><jupyter_text>为了能够使用该方法，可以添加 __len__ 这一dunder 方法：<jupyter_code>class LenSupport:
    def __len__(self):
        return 42

obj = LenSupport()
len(obj)<jupyter_output><empty_output><jupyter_text>另一个比较常见的例子是 slicing，可以通过实现 __getitem__ 方法来使得自己能执行类似这样的切片操作：obj[start:stop].这种优雅的设计被称为Python数据模型，使开发人员可以利用丰富的语言功能，例如序列，迭代，运算符重载，属性访问等。可以将Python的数据模型视为一种强大的API，可以通过实现一个或多个dunder方法来与之交互。如果想编写更多Python风格代码，那么知道如何以及何时使用dunder方法是重要的一步。对于初学者来说，起初可能有点不知所措。本节就以一个简单的Account类为例，记录使用dunder的方法，解锁以下语言功能：- 初始化新对象- 对象表示- 启用迭代- 运算符重载（比较）- 运算符重载（加法）- 方法调用- 上下文管理器支持（with声明）  对象初始化: __init__这个是最常用的，之前的笔记中也已经提到了，就是初始化类对象<jupyter_code>class Account:
    """A simple account class"""

    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []
acc = Account('bob')  # default amount = 0
acc = Account('bob', 10)<jupyter_output><empty_output><jupyter_text>对象表示: __str__, __repr__在Python中，通常的做法是为类的使用者提供对象的字符串表示形式（有点类似于API文档）使用dunder方法有两种方法可以做到这一点：1. __repr__：对象的“正式”字符串表示形式。这就是使该类成为对象的方式。__repr__ 的目标是明确的。2. __str__：对象的“非正式”或可很好打印的字符串表示形式。这是给最终用户的。让我们在Account类上实现这两个方法：<jupyter_code>class Account:
    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []

    def __repr__(self):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    def __str__(self):
        return 'Account of {} with starting amount: {}'.format(
            self.owner, self.amount)<jupyter_output><empty_output><jupyter_text>如果不想硬编码"Account"用作类的名称，则也可以使用 self.__class__.__name__ 访问。如果只想在Python类上实现这两个to-string方法之一，请确保它是__repr__。现在，可以通过多种方式查询对象，并且始终获得漂亮的字符串表示形式：<jupyter_code>acc = Account('bob', 10)
str(acc)
print(acc)
repr(acc)<jupyter_output><empty_output><jupyter_text>迭代：__len__，__getitem__，__reversed__为了遍历对象，需要添加一些交易。因此，首先，定义一个简单的方法来添加事务：<jupyter_code>class Account:
    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []

    def __repr__(self):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    def __str__(self):
        return 'Account of {} with starting amount: {}'.format(
            self.owner, self.amount)
    def add_transaction(self, amount):
        if not isinstance(amount, int):
            raise ValueError('please use int for amount')
        self._transactions.append(amount)
    
    @property
    def balance(self):
        return self.amount + sum(self._transactions)<jupyter_output><empty_output><jupyter_text>还定义了一个属性来计算帐户的余额，因此可以方便地使用account.balance。此方法采用起始金额，并添加所有事务的总和。现在在该帐户上进行一些存款和取款：<jupyter_code>acc = Account('bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
acc.balance<jupyter_output><empty_output><jupyter_text>现在我有一些数据，我想知道：1. 那里有几笔交易？2. 为帐户对象编制索引以获取交易号…3. 循环交易使用类定义，我目前无法做到这一点。以下所有语句均引发TypeError异常：<jupyter_code>len(acc)
for t in acc:
    print(t)
acc[1]<jupyter_output><empty_output><jupyter_text>利用dunder方法，只需要一点代码就可以使该类可迭代：<jupyter_code>class Account:
    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []

    def __repr__(self):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    def __str__(self):
        return 'Account of {} with starting amount: {}'.format(
            self.owner, self.amount)
    def add_transaction(self, amount):
        if not isinstance(amount, int):
            raise ValueError('please use int for amount')
        self._transactions.append(amount)
    
    @property
    def balance(self):
        return self.amount + sum(self._transactions)
    
    def __len__(self):
        return len(self._transactions)

    def __getitem__(self, position):
        return self._transactions[position]
acc = Account('bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
acc.balance<jupyter_output><empty_output><jupyter_text>现在再执行前面报错的语句：<jupyter_code>len(acc)
for t in acc:
    print(t)
acc[1]<jupyter_output><empty_output><jupyter_text>要以相反的顺序遍历，可以实现__reversed__特殊的方法：<jupyter_code>class Account:
    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []

    def __repr__(self):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    def __str__(self):
        return 'Account of {} with starting amount: {}'.format(
            self.owner, self.amount)
    def add_transaction(self, amount):
        if not isinstance(amount, int):
            raise ValueError('please use int for amount')
        self._transactions.append(amount)
    
    @property
    def balance(self):
        return self.amount + sum(self._transactions)
    
    def __len__(self):
        return len(self._transactions)

    def __getitem__(self, position):
        return self._transactions[position]
    
    def __reversed__(self):
        return self[::-1]
    
acc = Account('bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
list(reversed(acc))<jupyter_output><empty_output><jupyter_text>重载运算符来比较: __eq__, __lt__<jupyter_code>2 > 1
'a' > 'b'<jupyter_output><empty_output><jupyter_text>这感觉很自然，但是实际上幕后发生的事情是令人惊讶的。为什么>在整数，字符串和其他对象（只要它们是相同的类型）上同样能很好地工作？ -- 因为这些对象实现了一个或多个比较dunder方法。一种简单的验证方法是使用dir()内置函数：<jupyter_code>dir('A')<jupyter_output><empty_output><jupyter_text>现在添加比较函数。为了不必实现所有比较dunder方法，使用functools.total_ordering装饰器，该装饰器允许仅实现__eq__和__lt__：<jupyter_code>from functools import total_ordering

@total_ordering
class Account:
    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []

    def __repr__(self):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    def __str__(self):
        return 'Account of {} with starting amount: {}'.format(
            self.owner, self.amount)
    def add_transaction(self, amount):
        if not isinstance(amount, int):
            raise ValueError('please use int for amount')
        self._transactions.append(amount)
    
    @property
    def balance(self):
        return self.amount + sum(self._transactions)
    
    def __len__(self):
        return len(self._transactions)

    def __getitem__(self, position):
        return self._transactions[position]
    
    def __reversed__(self):
        return self[::-1]

    def __eq__(self, other):
        return self.balance == other.balance

    def __lt__(self, other):
        return self.balance < other.balance
acc = Account('bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
acc.balance

acc2 = Account('tim', 100)
acc2.add_transaction(20)
acc2.add_transaction(40)
acc2.balance

acc2 > acc<jupyter_output><empty_output><jupyter_text>重载： __add__在Python中，一切都是对象。我们完全可以使用+（plus）运算符添加两个整数或两个字符串，可以重载该运算符<jupyter_code>from functools import total_ordering

@total_ordering
class Account:
    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []

    def __repr__(self):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    def __str__(self):
        return 'Account of {} with starting amount: {}'.format(
            self.owner, self.amount)
    def add_transaction(self, amount):
        if not isinstance(amount, int):
            raise ValueError('please use int for amount')
        self._transactions.append(amount)
    
    @property
    def balance(self):
        return self.amount + sum(self._transactions)
    
    def __len__(self):
        return len(self._transactions)

    def __getitem__(self, position):
        return self._transactions[position]
    
    def __reversed__(self):
        return self[::-1]

    def __eq__(self, other):
        return self.balance == other.balance

    def __lt__(self, other):
        return self.balance < other.balance
    
    def __add__(self, other):
        owner = self.owner + other.owner
        start_amount = self.balance + other.balance
        return Account(owner, start_amount)
acc = Account('bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
acc.balance

acc2 = Account('tim', 100)
acc2.add_transaction(20)
acc2.add_transaction(40)
acc2.balance

acc3 = acc2 + acc
acc3<jupyter_output><empty_output><jupyter_text>可调用的Python对象： __call__通过添加__call__ dunder方法，可以使对象像常规函数一样可调用。对于我们的帐户类，我们可以打印一个组成其余额的所有交易的漂亮报告：<jupyter_code>from functools import total_ordering

@total_ordering
class Account:
    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []

    def __repr__(self):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    def __str__(self):
        return 'Account of {} with starting amount: {}'.format(
            self.owner, self.amount)
    def add_transaction(self, amount):
        if not isinstance(amount, int):
            raise ValueError('please use int for amount')
        self._transactions.append(amount)
    
    @property
    def balance(self):
        return self.amount + sum(self._transactions)
    
    def __len__(self):
        return len(self._transactions)

    def __getitem__(self, position):
        return self._transactions[position]
    
    def __reversed__(self):
        return self[::-1]

    def __eq__(self, other):
        return self.balance == other.balance

    def __lt__(self, other):
        return self.balance < other.balance
    
    def __add__(self, other):
        owner = self.owner + other.owner
        start_amount = self.balance + other.balance
        return Account(owner, start_amount)
    
    def __call__(self):
        print('Start amount: {}'.format(self.amount))
        print('Transactions: ')
        for transaction in self:
            print(transaction)
        print('\nBalance: {}'.format(self.balance))
acc = Account('bob', 10)
acc.add_transaction(20)
acc.add_transaction(-10)
acc.add_transaction(50)
acc.add_transaction(-20)
acc.add_transaction(30)
acc()<jupyter_output>Start amount: 10
Transactions: 
20
-10
50
-20
30

Balance: 80<jupyter_text>上下文管理支持和With声明：__enter__，__exit__这里展示的最后一个示例是有关Python的一个稍微高级的概念：上下文管理器，并增加了对该with语句的支持。Python中的“上下文管理器”是什么？上下文管理器是对象需要遵循的简单“协议”（或接口），因此可以与with语句一起使用。基本上，如果您希望对象充当上下文管理器，则只需为对象添加__enter__和__exit__方法。让我们使用上下文管理器支持将回滚机制添加到我们的Account类中。如果余额在添加另一笔交易后变为负数，我们将回滚到先前的状态。<jupyter_code>from functools import total_ordering

@total_ordering
class Account:
    def __init__(self, owner, amount=0):
        """
        This is the constructor that lets us create
        objects from this class
        """
        self.owner = owner
        self.amount = amount
        self._transactions = []

    def __repr__(self):
        return 'Account({!r}, {!r})'.format(self.owner, self.amount)

    def __str__(self):
        return 'Account of {} with starting amount: {}'.format(
            self.owner, self.amount)
    def add_transaction(self, amount):
        if not isinstance(amount, int):
            raise ValueError('please use int for amount')
        self._transactions.append(amount)
    
    @property
    def balance(self):
        return self.amount + sum(self._transactions)
    
    def __len__(self):
        return len(self._transactions)

    def __getitem__(self, position):
        return self._transactions[position]
    
    def __reversed__(self):
        return self[::-1]

    def __eq__(self, other):
        return self.balance == other.balance

    def __lt__(self, other):
        return self.balance < other.balance
    
    def __add__(self, other):
        owner = self.owner + other.owner
        start_amount = self.balance + other.balance
        return Account(owner, start_amount)
    
    def __call__(self):
        print('Start amount: {}'.format(self.amount))
        print('Transactions: ')
        for transaction in self:
            print(transaction)
        print('\nBalance: {}'.format(self.balance))
        
    def __enter__(self):
        print('ENTER WITH: Making backup of transactions for rollback')
        self._copy_transactions = list(self._transactions)
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('EXIT WITH:', end=' ')
        if exc_type:
            self._transactions = self._copy_transactions
            print('Rolling back to previous transactions')
            print('Transaction resulted in {} ({})'.format(
                exc_type.__name__, exc_val))
        else:
            print('Transaction OK')
def validate_transaction(acc, amount_to_add):
    with acc as a:
        print('Adding {} to account'.format(amount_to_add))
        a.add_transaction(amount_to_add)
        print('New balance would be: {}'.format(a.balance))
        if a.balance < 0:
            raise ValueError('sorry cannot go in debt!')
acc4 = Account('sue', 10)

print('\nBalance start: {}'.format(acc4.balance))
validate_transaction(acc4, 20)

print('\nBalance end: {}'.format(acc4.balance))
acc4 = Account('sue', 10)

print('\nBalance start: {}'.format(acc4.balance))
try:
    validate_transaction(acc4, -50)
except ValueError as exc:
    print(exc)

print('\nBalance end: {}'.format(acc4.balance))<jupyter_output>Balance start: 10
ENTER WITH: Making backup of transactions for rollback
Adding -50 to account
New balance would be: -40
EXIT WITH: Rolling back to previous transactions
Transaction resulted in ValueError (sorry cannot go in debt!)
sorry cannot go in debt!

Balance end: 10<jupyter_text>了解 dunder 方法可以帮助写出更加python风格的代码。但是dunder方法比较多，可以根据自己需要看情况来运用。  Side Effects主要参考：[What is a side-effect of a function in Python?](https://dev.to/dev0928/what-is-a-side-effect-of-a-function-in-python-36ei:~:text=A%20function%20is%20said%20to,gets%20updated%20within%20the%20function.)任何有意义的函数或过程都需要其调用环境中的某种数据才能产生有意义的结果。如果发送到函数的数据在函数内发生变化，会出现什么情况？我们说一个函数有side-effect，即副作用，就是当在函数内，给的数据或任何其他比如全局变量所提供的值被更新。从一些基本术语开始，然后再尝试进一步解释Python中的副作用。什么是parameters？作为输入传递给函数的数据称为参数。以下函数中的num1和num2是参数。<jupyter_code>def my_sum(num1, num2):
    return num1 + num2<jupyter_output><empty_output><jupyter_text>什么是arguments？函数调用期间提供的值称为arguments。因此，以下函数调用中的val1和val2是参数。尽管这俩术语经常被互换使用，中文也都翻译成参数，不过更准确地说，parameter是函数内参数，而argument是传递给函数的参数。<jupyter_code>val1 = 10
val2 = 20
ans = my_sum(val1,val2)<jupyter_output><empty_output><jupyter_text>通过值和引用传递参数是什么？如果在对提供的参数的函数调用期间创建了新的参数副本，则认为参数是通过value传递的。如果将相同变量的引用传递给函数，则参数由reference传递。在Python中,参数是通过值传递还是通过引用传递？Python通过在函数调用期间共享对象引用来传递参数。使用下面的函数示例检查Python的行为：<jupyter_code>def ref_copy_demo(x):
    print(f"x = {x}, id = {id(x)}")
    x += 45
    print(f"x = {x}, id = {id(x)}")


num = 10
print(f"before function call - num = {num}, id = {id(num)}") 
ref_copy_demo(num)
print(f"after function call - num = {num}, id = {id(num)}")<jupyter_output>before function call - num = 10, id = 2173715901008
x = 10, id = 2173715901008
x = 55, id = 2173716090928
after function call - num = 10, id = 2173715901008<jupyter_text>这是上述函数调用的说明：![](pictures/6056x8a7rwk1hv9h4oot.png)分析函数调用及其输出：- 在上面的调用中使用了id函数来获取对象的标识值。id函数返回一个整数，该整数在此对象的生存期内是唯一且恒定的。它有助于我们跟踪对象是否通过是引用传递给函数的。- 请注意，对于变量值更改前后传递的参数，id值已更改。- 这意味着直到parameter值没有变化时，函数内部的parameter 还与argument 保持相同。Python保留argument 的引用传递。但是，一旦 parameter 被更新，parameter 的本地副本就会被保留，而argument的值将保持不变。什么是副作用？如果函数更改了函数定义之外的任何内容（例如更改传递给函数的参数或更改全局变量），则称其具有副作用。例如：<jupyter_code>def fn_side_effects(fruits):
    print(f"Fruits before change - {fruits} id - {id(fruits)}")
    fruits += ["pear", "banana"]
    print(f"Fruits after change - {fruits} id - {id(fruits)}")

fruit_list = ["apple", "orange"]
print(f"Fruits List before function call - {fruit_list} id - {id(fruit_list)}")
fn_side_effects(fruit_list)
print(f"Fruits List after function call - {fruit_list} id - {id(fruit_list)}")<jupyter_output>Fruits List before function call - ['apple', 'orange'] id - 2173789222016
Fruits before change - ['apple', 'orange'] id - 2173789222016
Fruits after change - ['apple', 'orange', 'pear', 'banana'] id - 2173789222016
Fruits List after function call - ['apple', 'orange', 'pear', 'banana'] id - 2173789222016<jupyter_text>因此，由于以下原因，此功能显然具有副作用：- ID值 argument 和parameter 完全相同。- 但在函数调用之后，Argument 中已添加了其他值。如何创建没有副作用的类似功能？<jupyter_code>def fn_no_side_effects(fruits):
    print(f"Fruits before change - {fruits} id - {id(fruits)}")
    fruits = fruits + ["pear", "banana"]
    print(f"Fruits after change - {fruits} id - {id(fruits)}")

fruit_list = ["apple", "orange"]
print(f"Fruits List before function call - {fruit_list} id - {id(fruit_list)}")
fn_no_side_effects(fruit_list)
print(f"Fruits List after function call - {fruit_list} id - {id(fruit_list)}")<jupyter_output>Fruits List before function call - ['apple', 'orange'] id - 2173789218496
Fruits before change - ['apple', 'orange'] id - 2173789218496
Fruits after change - ['apple', 'orange', 'pear', 'banana'] id - 2173789222016
Fruits List after function call - ['apple', 'orange'] id - 2173789218496
{'fraction_non_alphanumeric': 0.12492200584545664, 'fraction_numerical': 0.022954911168762932, 'mean_word_length': 4.2868055555555555, 'pattern_counts': {'":': 0, '<': 152, '<?xml version=': 0, '>': 151, 'https://': 9, 'lorem ipsum': 0, 'www.': 5, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '41', 'dir': 'jupyter-structured-clean-dedup', 'id': '159519', 'license': 'MIT', 'n_tokens_mistral': 14207, 'n_tokens_neox': 14597, 'n_words': 1845, 'path': '1-learn-python/4-python-magic.ipynb', 'repo_name': 'OuyangWenyu/hydrus', 'size': '30451'}