[python 进阶] 第7章 函数装饰器和闭包

文章目录

    • 7.1 装饰器基础知识
    • 7.2 Python何时执行装饰器
    • 7.3 使用装饰器改进“策略”
    • 7.4 变量作用域(global)
    • 备注 -比较字节码(暂略)
    • 7.5 闭包
    • 7.6 nonlocal声明
    • global和nonlocal的区别
    • 7.7 实现一个简单的装饰器
    • 7.8 标准库中的装饰器
      • 7.8.1 使用functools.lru_cache做备忘
      • 补充 @functools.lru_cache()可以配置参数
      • 7.8.2 单分派泛函数( @functools.singledispatch)
    • 7.9 叠放装饰器
    • 7.10 参数化装饰器
      • 7.10.1 一个参数化的注册装饰器
      • 7.10.2 参数化clock装饰器

函数装饰器用于在源码中“标记”函数,以某种方式增强函数的行为。这是一项强大的功能,但是若想掌握,必须理解闭包。
nonlocal 是新近出现的保留关键字,在 Python 3.0 中引入。
除了在装饰器中有用处之外,闭包还是 回调式异步编程函数式编程风格的基础。
本章的最终目标是解释清楚函数装饰器的工作原理,包括最简单的 注册装饰器和较复杂的 参数化装饰器。但是,在实现这一目标之前,我们要讨论下述话题:

  • Python 如何计算装饰器句法
  • Python 如何判断变量是不是局部的
  • 闭包存在的原因和工作原理
  • nonlocal 能解决什么问题
    掌握这些基础知识后,我们可以进一步探讨装饰器:
  • 实现行为良好的装饰器
  • 标准库中有用的装饰器
  • 实现一个参数化装饰器

7.1 装饰器基础知识

装饰器是可调用的对象,其参数是另一个函数(被装饰的函数)。 装饰器可能会处理被装饰的函数,然后把它返回,或者将其替换成另一个函数或可调用对象。
假如有个名为 decorate 的装饰器:

@decorate
def target():
print('running target()')

上述代码的效果与下述写法一样:

def target():
print('running target()')
target = decorate(target)

两种写法的最终结果一样:上述两个代码片段执行完毕后得到的 target 不一定是原来那
个 target 函数,而是 decorate(target) 返回的函数。

为了确认被装饰的函数会被替换,请看示例 7-1 中的控制台会话。
示例 7-1 装饰器通常把函数替换成另一个函数

>>> def deco(func):
... def inner():
... print('running inner()')
... return inner 
...
>>> @deco
... def target(): 
... print('running target()')
...
>>> target() 
running inner()
>>> target 
<function deco.<locals>.inner at 0x10063b598>

严格来说,装饰器只是语法糖。如前所示,装饰器可以像常规的可调用对象那样调用,其
参数是另一个函数。有时,这样做更方便,尤其是做元编程(在运行时改变程序的行
为)时。
综上,装饰器的一大特性是,能把被装饰的函数替换成其他函数。第二个特性是,装饰器
在加载模块时立即执行。

7.2 Python何时执行装饰器

装饰器的一个关键特性是,它们在被装饰的函数定义之后立即运行。这通常是在导入时
(即 Python 加载模块时)

registry = []
def register(func):print('running register(%s)' % func)registry.append(func)return func
@register
def f1():print('running f1()')
@register
def f2():print('running f2()')
def f3():print('running f3()')
def main():print('running main()')print('registry ->', registry)  # 发现registry这个数组并不是空f1()f2()f3()
if __name__=='__main__':main()

输出后是什么样子呢?

running register(<function f1 at 0x7ff079e400d0>)
running register(<function f2 at 0x7ff06c9e37b8>)
running main()
registry -> [<function f1 at 0x7ff079e400d0>, <function f2 at 0x7ff06c9e37b8>]
running f1()
running f2()
running f3()

注意:在调用f1()和f2()时,输出的是 runnint f1()和running f2()。
上面的例子主要是强调:函数装饰器在导入模块时立即执行,而被装饰的函数只在明确调用时运行。这突出了 Python 程序员所说的导入时和运行时之间的区别。
考虑到装饰器在真实代码中的常用方式,示例 7-2 有两个不寻常的地方。装饰器函数与被装饰的函数在同一个模块中定义。实际情况是,装饰器通常在一个模块中定义,然后应用到其他模块中的函数上。
register 装饰器返回的函数与通过参数传入的相同。实际上,大多数装饰器会在内部定义一个函数,然后将其返回。

虽然上示例中的 register 装饰器原封不动地返回被装饰的函数,但是这种技术并非没有用处。很多 Python Web 框架使用这样的装饰器把函数添加到某种中央注册处,例如把URL模式映射到生成 HTTP 响应的函数上的注册处。这种注册装饰器可能会也可能不会修改被装饰的函数。

7.3 使用装饰器改进“策略”

使用注册装饰器可以改进之前的第六章中的电商促销折扣示例。
回顾一下,示例 6-6 的主要问题是,定义体中有函数的名称,但是 best_promo 用来判断哪个折扣幅度最大的 promos 列表中也有函数名称。这种重复是个问题,因为新增策略函数后可能会忘记把它添加到 promos 列表中,导致 best_promo 忽略新策略,而且不报错,为系统引入了不易察觉的缺陷。以下这个例子使用注册装饰器解决了这个问题。

  • promos 列表中的值使用 promotion 装饰器

      from collections import namedtupleCustomer = namedtuple('Customer', 'name fidelity')class LineItem:def __init__(self, product, quantity, price):self.product = productself.quantity = quantityself.price = pricedef total(self):return self.price * self.quantitypromos =[]def promotion(promo_func):promos.append(promo_func)return promo_func@promotiondef fidelity(order):"""为积分为1000或者以上的顾客提供5%折扣"""return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0@promotiondef bulk_item(order):"""单个商品为20个或者以上时提供10%折扣"""discount = 0for item in order.cart:if item.quantity >= 20:discount += item.total() * 0.01return discount@promotiondef large_order(order):"""订单中的不同商品达到10个以上时提供7%折扣"""distinct_items = {item.product for item in order.cart}if len(distinct_items) >= 10:return order.total() * 0.07return 0def best_promo(order):"""选择可用的最佳折扣"""return max(promo(order) for promo in promos)class Order: #上下文def __init__(self, customer, cart, promotion=None):self.customer = customerself.cart = list(cart)self.promotion = promotiondef total(self):if not hasattr(self, '__total'):self.__total = sum(item.total() for item in self.cart)return self.__totaldef due(self):if self.promotion is None:discount = 0else:discount = self.promotion(self)return self.total() - discountdef __repr__(self):fmt = '<Order total: {:.2f} due: {:.2f}>'return fmt.format(self.total(), self.due())if __name__ == '__main__':joe = Customer('John Doe', 0)ann = Customer('Ann Smith', 1000)cart = [LineItem('banana', 4, 0.5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)]print(Order(joe, cart, fidelity))   # <Order total: 42.00 due: 42.00>print(Order(ann, cart, fidelity))   #<Order total: 42.00 due: 39.90>
    

与 6.1 节给出的方案相比,这个方案有几个优点。

  • 促销策略函数无需使用特殊的名称(即不用以 _promo 结尾)。
  • @promotion 装饰器突出了被装饰的函数的作用,还便于临时禁用某个促销策略:只需把装饰器注释掉。
  • 促销折扣策略可以在其他模块中定义,在系统中的任何地方都行,只要使用@promotion 装饰即可。

不过,多数装饰器会修改被装饰的函数。通常,它们会定义一个内部函数,然后将其返
回,替换被装饰的函数。使用内部函数的代码几乎都要靠闭包才能正确运作。

7.4 变量作用域(global)

在示例 7-4 中,我们定义并测试了一个函数,它读取两个变量的值:一个是局部变量 a,
是函数的参数;另一个是变量 b,这个函数没有定义它。

示例 7-4 一个函数,读取一个局部变量和一个全局变量

>>> def f1(a):
... print(a)
... print(b)
...
>>> f1(3)
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f1
NameError: global name 'b' is not defined

出现错误并不奇怪。 在示例 7-4 中,如果先给全局变量 b 赋值,然后再调用 f,那就不
会出错。

示例 7-5 b 是局部变量,因为在函数的定义体中给它赋值了

>>> b = 6
>>> def f2(a):
... print(a)
... print(b)
... b = 9
...
>>> f2(3)
3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 3, in f2
UnboundLocalError: local variable 'b' referenced before assignment

注意,首先输出了 3,这表明 print(a) 语句执行了。但是第二个语句 print(b) 执行不了。一开始我很吃惊,我觉得会打印 6,因为有个全局变量 b,而且是在 print(b) 之后为局部变量 b 赋值的。
可事实是,Python 编译函数的定义体时,它判断 b 是局部变量,因为在函数中给它赋值了。生成的字节码证实了这种判断,Python 会尝试从本地环境获取 b。后面调用 f2(3)时, f2 的定义体会获取并打印局部变量 a 的值,但是尝试获取局部变量 b 的值时,发现b 没有绑定值。
这不是缺陷,而是设计选择:Python 不要求声明变量,但是假定在函数定义体中赋值的变量是局部变量。这比 JavaScript 的行为好多了,JavaScript 也不要求声明变量,但是如果忘记把变量声明为局部变量(使用 var),可能会在不知情的情况下获取全局变量。
如果在函数中赋值时想让解释器把 b 当成全局变量,要使用 global 声明:

>>> b = 6
>>> def f3(a):
... global b
... print(a)
... print(b)
... b = 9
...
>>> f3(3)
3
6
>>> b
9
>>> f3(3)
3
9
>>> b = 30
>>> b
30
>>>

了解 Python 的变量作用域之后,下一节可以讨论闭包了。如果好奇示例 7-4 和示例 7-5 中的两个函数生成的字节码有什么区别,可以下面的备注:

备注 -比较字节码(暂略)

7.5 闭包

在博客圈,人们有时会把闭包和匿名函数弄混。这是有历史原因的:在函数内部定义函数不常见,直到开始使用匿名函数才会这样做。而且,只有涉及嵌套函数时才有闭包问题。
因此,很多人是同时知道这两个概念的。
其实,闭包指延伸了作用域的函数,其中包含函数定义体中引用、但是不在定义体中定义的非全局变量。函数是不是匿名的没有关系,关键是它能访问定义体之外定义的非全局变量。
这个概念难以掌握,最好通过示例理解。
假如有个名为 avg 的函数,它的作用是计算不断增加的系列值的均值;例如,整个历史中某个商品的平均收盘价。每天都会增加新价格,因此平均值要考虑至目前为止所有的价格。

示例 7-8 average_oo.py:计算移动平均值

class Averager():def __init__(self):self.series=[]def __call__(self, new_value):self.series.append(new_value)total = sum(self.series)return total/len(self.series)
avg=Averager()
print(avg(10)) #10.0
print(avg(11)) #10.5
print(avg(12)) #11.0

Averager 的实例是可调用对象

示例 7-9 是函数式实现,使用高阶函数 make_averager。

示例 7-9 average.py:计算移动平均值的高阶函数

def make_averager():series = []def averager(new_value):series.append(new_value)total = sum(series)return total/len(series)
return averager

调用 make_averager 时,返回一个 averager 函数对象。每次调用 averager 时,它会
把参数添加到系列值中,然后计算当前平均值,如示例 7-10 所示。
示例 7-10 测试示例 7-9

>>> avg = make_averager()
>>> avg(10)
10.0
>>> avg(11)
10.5
>>> avg(12)
11.0

注意,这两个示例有共通之处:调用 Averager() 或 make_averager() 得到一个可调用对象 avg,它会更新历史值,然后计算当前均值。在示例 7-8 中,avg 是 Averager 的实例;在示例 7-9 中是内部函数 averager。不管怎样,我们都只需调用 avg(n),把 n 放入系列值中,然后重新计算均值。
Averager 类的实例 avg 在哪里存储历史值很明显:self.series 实例属性。但是第二个示例中的 avg 函数在哪里寻找 series 呢?
注意,series 是 make_averager 函数的局部变量,因为那个函数的定义体中初始化了series:series = []。可是,调用 avg(10) 时,make_averager 函数已经返回了,而它的本地作用域也一去不复返了。
在 averager 函数中,series 是自由变量(free variable)。这是一个技术术语,指未在本地作用域中绑定的变量。

在这里插入图片描述
averager 的闭包延伸到那个函数的作用域之外,包含自由变量 series 的绑定。
综上,闭包是一种函数,它会保留定义函数时存在的自由变量的绑定,这样调用函数时,虽然定义作用域不可用了,但是仍能使用那些绑定。
注意,只有嵌套在其他函数中的函数才可能需要处理不在全局作用域中的外部变量。

7.6 nonlocal声明

前面实现 make_averager 函数的方法效率不高。在示例 7-9 中,我们把所有值存储在历史数列中,然后在每次调用 averager 时使用 sum 求和。更好的实现方式是,只存储目前的总值和元素个数,然后使用这两个数计算均值。
示例 7-13 中的实现有缺陷,只是为了阐明观点。你能看出缺陷在哪儿吗?

示例 7-13 计算移动平均值的高阶函数,不保存所有历史值,但有缺陷
def make_averager():
count = 0
total = 0def averager(new_value):count += 1total += new_valuereturn total / count
return averager

Python 3 引入了 nonlocal 声明。它的作用是把变量标记为自由变量,即使在函数中为变量赋予新值了,也会变成自由变量。如果为 nonlocal 声明的变量赋予新值,闭包中保存的绑定会更新。

示例 7-14 计算移动平均值,不保存所有历史(使用 nonlocal 修正)
def make_averager():
count = 0
total = 0def averager(new_value):nonlocal count, totalcount += 1total += new_valuereturn total / count
return averager

global和nonlocal的区别

global 表示将变量声明为全局变量
nonlocal 表示将变量声明为外层变量(外层函数的局部变量,而且不能是全局变量)。

    1. global关键字用来在函数或其他局部作用域中使用全局变量。但是如果不修改全局变量也可以不使用global关键字。
    1. 声明全局变量,如果在局部要对全局变量修改,需要在局部也要先声明该全局变量。

         gcount = 0def global_test():global gcountgcount +=1print (gcount)global_test()
      
  • 3.在局部如果不声明全局变量,并且不修改全局变量。则可以正常使用全局变量:

      			gcount = 0def global_test():print(gcount)global_test()
    
    1. nonlocal关键字用来在函数或其他作用域中使用外层(非全局)变量

         def make_counter(): count = 0 def counter(): nonlocal count count += 1 return count return counter def make_counter_test(): mc = make_counter() print(mc())print(mc())print(mc())make_counter_test()
      

7.7 实现一个简单的装饰器

定义了一个装饰器,它会在每次调用被装饰的函数时计时,然后把经过的时间、传入的参数和调用的结果打印出来。
import time

def clock(func):def clocked(*args):t0=time.perf_counter()result = func(*args)elapsed = time.perf_counter()name = func.__name__arg_str = ','.join(repr(arg) for arg in args)print('[%0.8fs] %s(%s)-> %r' % (elapsed, name, arg_str, result))return resultreturn clocked@clock
def snooze(seconds):time.sleep(seconds)
@clock
def factorial(n):return 1 if n < 2 else n*factorial(n-1)
if __name__ == '__main__':print('*' * 40, 'Calling snooze(.123)')snooze(.123)print('*' * 40, 'Calling factorial(6)')print('6! =', factorial(6))

这是装饰器的典型行为:把被装饰的函数替换成新函数,二者接受相同的参数,而且(通常)返回被装饰的函数本该返回的值,同时还会做些额外操作。

将上述例子改进一下:

7.8 标准库中的装饰器

Python 内置了三个用于装饰方法的函数:property、classmethod 和
staticmethod。另一个常见的装饰器是 functools.wraps,它的作用是协助构建行为良好的装饰器。我
们在示例 7-17 中用过。标准库中最值得关注的两个装饰器是 lru_cache 和全新的
singledispatch(Python 3.4 新增)。这两个装饰器都在 functools 模块中定义。

7.8.1 使用functools.lru_cache做备忘

示例 7-18 生成第 n 个斐波纳契数,递归方式非常

import timedef clock(func):def clocked(*args):print(*args)t0 = time.perf_counter()result = func(*args)elapsed = time.perf_counter()name = func.__name__arg_str = ','.join(repr(arg) for arg in args)print('[%0.8fs] %s(%s)-> %r' % (elapsed, name, arg_str, result))return resultreturn clocked@clock
def fibonacci(n):return n if n < 2 else fibonacci(n-2)+fibonacci(n-1)if __name__ == '__main__':print(fibonacci(6))

示例 7-19 使用缓存实现,速度

import time
import functools
def clock(func):def clocked(*args):print(*args)t0 = time.perf_counter()result = func(*args)elapsed = time.perf_counter()name = func.__name__arg_str = ','.join(repr(arg) for arg in args)print('[%0.8fs] %s(%s)-> %r' % (elapsed, name, arg_str, result))return resultreturn clocked@functools.lru_cache() #注意,必须像常规函数那样调用 lru_cache。这一行中有一对括号:@functools.lru_cache()。这么做的原因是,lru_cache 可以接受配置参数
@clock
def fibonacci(n):return n if n < 2 else fibonacci(n-2)+fibonacci(n-1)if __name__ == '__main__':print(fibonacci(6))

补充 @functools.lru_cache()可以配置参数

7.8.2 单分派泛函数( @functools.singledispatch)

7.9 叠放装饰器

示例 7-19 演示了叠放装饰器的方式:@lru_cache 应用到 @clock 装饰 fibonacci 得到的结果上。在示例 7-21 中,模块中最后一个函数应用了两个 @htmlize.register 装饰器。
把 @d1 和 @d2 两个装饰器按顺序应用到 f 函数上,作用相当于 f = d1(d2(f))。
也就是说,下述代码:

@d1
@d2
def f():print('f')

等同于:

def f():print('f')f = d1(d2(f))

除了叠放装饰器之外,本章还用到了几个接受参数的装饰器,

7.10 参数化装饰器

解析源码中的装饰器时,Python 把被装饰的函数作为第一个参数传给装饰器函数。那怎么让装饰器接受其他参数呢?答案是:创建一个装饰器工厂函数,把参数传给它,返回一个装饰器,然后再把它应用到要装饰的函数上。

registry = []
def register(func):print('running register(%s)' % func)registry.append(func)return func
@register
def f1():print('running f1()')
print('running main')
print('registry ->',registry)
f1()

7.10.1 一个参数化的注册装饰器

为了便于启用或禁用 register 执行的函数注册功能,我们为它提供一个可选的 active参数,设为 False 时,不注册被装饰的函数。实现方式参见下面这个例子。从概念上看,这个新的 register 函数不是装饰器,而是装饰器工厂函数。调用它会返回真正的装饰器,这才是应用到目标函数上的装饰器。

registry = []
def register(active=True):def decorate(func):print('running register(active=%s)->decorate(%s)'%(active, func))if active:registry.add(func)else:registry.discard(func)return func #decorate 是装饰器,必须返回一个函数return decorate # register 是装饰器工厂函数,因此返回 decorate
@register(active=True)
def f1():print('running f1()')
@register() #即使不传入参数,register 也必须作为函数调用(@register()),即要返回真正的装饰器 decorate。
def f2():print('running f2()')
def f3():print('running f3()')

如果不使用 @ 句法,那就要像常规函数那样使用 register;若想把 f 添加到 registry中,则装饰 f 函数的句法是 register()(f);不想添加(或把它删除)的话,句法是register(active=False)(f)。

参数化装饰器的原理相当复杂,我们刚刚讨论的那个比大多数都简单。参数化装饰器通常会把被装饰的函数替换掉,而且结构上需要多一层嵌套。

7.10.2 参数化clock装饰器

本节再次探讨 clock 装饰器,为它添加一个功能:让用户传入一个格式字符串,控制被装饰函数的输出。
import time
DEFAULT_FMT=’[{elapsed:0.8f}s] {name}({args})-> {result}’

def clock(fmt=DEFAULT_FMT):def decorate(func):def clocked(*_args):t0 = time.time()_result= func(*_args)elapsed=time.time-t0name = func._name__args = ','.join(repr(arg) for arg in _args)result = repr(_result)print(fmt.format(**locals()))return _resultreturn clockedreturn decorate
if __name__ == '__main__':@clockdef snooze(seconds):time.sleep(seconds)for i in range(3):snooze(.123)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/273795.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

自制“低奢内”CSS3登入表单,包含JS验证,请别嫌弃哦。

要求 必备知识 基本了解CSS语法,初步了解CSS3语法知识。和JS/JQuery基本语法。 开发环境 Adobe Dreamweaver CS6 演示地址 演示地址 预览截图(抬抬你的鼠标就可以看到演示地址哦): 制作步骤: 一, html结构 <div id"home"><form id"login" class…

class里面只能写以下5种

转载于:https://www.cnblogs.com/phplearnings/p/3650849.html

【排序】算法(python实现)

文章目录python 排序算法1 插入排序1.1 直接插入排序算法思想1.2 希尔排序算法思想2. 选择排序2.1 简单选择排序2.2 堆排序参考python 排序算法 1 插入排序 1.1 直接插入排序 算法思想 直接插入排序的核心思想就是&#xff1a;将数组中的所有元素依次跟前面已经排好的元素相…

OpenSSL漏洞补救办法详解(转)

CVE-2014-0160漏洞背景 2014年4月7日OpenSSL发布了安全公告&#xff0c;在OpenSSL1.0.1版本中存在严重漏洞(CVE-2014-0160)。OpenSSL Heartbleed模块存在一个BUG&#xff0c;问题存在于ssl/dl_both.c文件中的心跳部分&#xff0c;当攻击者构造一个特殊的数据包&#xff0c;满足…

SharePoint 自定义WebPart之间的连接

1、创建SharePoint解决方案&#xff0c;添加两个WebPart分别用来发送和接收&#xff1b; 2、发送值的WebPart需要继承自IWebPartField(当然&#xff0c;根据需要还可以选择IWebPartField,IWebPartParameters,IWebPartRow,IWebPartTable&#xff0c;具体参见msdn)&#xff0c;原…

C# 基础备忘录

1. decimal 类型调用ToString()方法后没把末尾的0去掉的解决办法: 例子&#xff1a;decimal? money Convert.ToDecimal(10.8950);string moneyStrmoney.Value.ToString(); 结果在同一台机子&#xff0c;两个项目里面会出现两个不同的结果。结果一&#xff1a;moneyStr"1…

PDFlib免费下载地址及详细介绍手册

PDFlib是一个用于创建PDF文档的开发工具,也可直接在你的服务器端产生PDF输出, 可利用PDFLib提供的简单易用的API&#xff08;应用编程接口&#xff09;在服务器或客户端产生PDF文档, PDFlib在生成PDF文档时不需要第3方软件的支持,也不需要其它工具。此产品属于产品 PDFlibPDI 的…

关于VS2010帮助文档的使用和VC6.0在Win7 64位下的使用

由于购置了新的电脑&#xff0c;安装的是Win7 64位的操作系统&#xff0c;这两天我在重新安装编程环境的时候遇到一些问题&#xff0c;现在都解决掉了&#xff0c;分享出来以供需要的人参考。 一、以前使用的是VS2008&#xff0c;从VC6到2008这么多年了一只使用的MSDN是带索引的…

谷歌Android各版本的代号变迁

简单回顾下Android发展历程2003年10月&#xff0c;Andy Rubin&#xff08;安迪鲁宾&#xff09;等人创建Android公司&#xff0c;并组建Android团队。2005年8月17日&#xff0c;Google低调收购了成立仅22个月的高科技企业Android及其团队。安迪鲁宾成为Google公司工程部副总裁&…

Map.Entry

如何简便的遍历Map 你是否已经对每次从Map中取得关键字然后再取得相应的值感觉厌倦&#xff1f; 使用JDK5的增强for循环&#xff0c;来遍历Map,简单多了&#xff0c;比Map.Entry还方便。 看代码&#xff1a; Java代码 for (String key : map.keySet()) { System.out.pri…

图解SQL的inner join(join)、left join、right join、full outer join、union、union all的区别...

对于SQL的Join&#xff0c;在学习起来可能是比较乱的。我们知道&#xff0c;SQL的Join语法有很多inner的&#xff0c;有outer的&#xff0c;有left的&#xff0c;有时候&#xff0c;对于Select出来的结果集是什么样子有点不是很清楚。Coding Horror上有一篇文章,通过文氏图 Ven…

数据库---四中连接查询(交叉、左连接、右连接、完整查询)

个人博客 &#xff1a;https://www.siyuan.run CSDN&#xff1a;https://blog.csdn.net/siyuan 微信小程序&#xff1a;思远Y 1、交叉连接查询 : (基本不适用---得到的是两张表数据的乘积) 语法&#xff1a;SELECT * FROM 表1,表2; PS&#xff1a;与表关系无关 示例&#xff…

数据库---练习题(45道)

准备工作 CREATE DATABASE STUDENTS; CREATE TABLE STUDENT( SNO VARCHAR(32) PRIMARY KEY NOT NULL, SNAME VARCHAR(32) NOT NULL, SSEX VARCHAR(32) NOT NULL, SBIRTHDAY DATETIME, CLASS VARCHAR(20) ); CREATE TABLE COURSE( CNO VARCHAR(20) PRIMARY KEY NOT NULL, CNAM…

数据库---JDBC

1.1 JDBC概述JDBC&#xff08;Java DataBase Connectivity,java数据库连接&#xff09;是一种用于执行SQL语句的Java API。JDBC是Java访问数据库的标准规范&#xff0c;可以为不同的关系型数据库提供统一访问&#xff0c;它由一组用Java语言编写的接口和类组成。 JDBC需要连接驱…

session中存放一个对象,只修改对象的属性,不将修改后的对象存放session,发现session中存放的对象也发生改变!

标题简单描述&#xff1a;先将一个对象放入session&#xff0c;只对对象属性值进行修改&#xff0c;但不将修改后的对象存放session中&#xff0c;发现session中存放的对象属性值也相对应的改变。Person personnew PerSon(); request.getSession().setAttribute("person&q…

利用三层交换机实现VLAN间路由配置

利用三层交换机实现VLAN间路由配置 实验目标&#xff1a; 一、 掌握交换机Tag VLAN的配置&#xff1b; 二、掌握三层交换机基本配置方法&#xff1b; 三、 掌握三层交换机的VLAN路由的配置方法&#xff1b; 四、通过三层交换机实现VLAN见相互通信&#xff1b; 技术原理&#xf…

easyui、表格中添加操作一列,将操作下设置为修改,点击修改弹出该行对象的编号。

页面中的代码(自己引入easy插件)&#xff1a; <body> <div id"table"></div> </body> <script type"text/javascript"> $(function(){$(#table).datagrid({ url:tt.json, //显示的数据striped:true, …

被LTRIM(RTRIM())害死了,差点

LTRIM(character_expression)去掉前置空格 LTRIM(RTRIM())就是把前置和后置空格都去掉。 character_expression可以是常量、变量或列。character_expression必须属于某个可隐式转换为varchar的数据类型(text、ntext和image除外)。否则&#xff0c;请使用CAST显示转换character_…

Mybatis、使用注解的方式编写用户和角色一对多关系,并使用延迟加载

1、数据库准备 CREATE TABLE role ( ID INT(11) NOT NULL COMMENT 编号,ROLE_NAME VARCHAR(30) DEFAULT NULL COMMENT 角色名称,ROLE_DESC VARCHAR(60) DEFAULT NULL COMMENT 角色描述,PRIMARY KEY (ID) ) ENGINEINNODB DEFAULT CHARSETutf8;INSERT INTO role(ID,ROLE_NAME,…

.Net中堆栈和堆的区别

首先堆栈和堆&#xff08;托管堆&#xff09;都在进程的虚拟内存中。&#xff08;在32位处理器上每个进程的虚拟内存为4GB&#xff09; 堆栈stack 1、堆栈中存储值类型 2、堆栈实际上是向下填充&#xff0c;即由高内存地址指向低内存地址填充 3、堆栈的工作方式是先分配内存的变…