summaryrefslogtreecommitdiff
path: root/PYTHON/Улучшаем код на Python.md
blob: 033629693ed71647cc641771292000dfa6bd6e49 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
![[telegram-cloud-photo-size-2-5382031937209757544-y.jpg]]

![[telegram-cloud-photo-size-2-5382031937209757546-y.jpg]]

![[telegram-cloud-photo-size-2-5399847483028401010-y.jpg]]


Уже почти никто не называет свои переменные непонятными буквами и имена функциям стараются давать начинающиеся с глагола, однако из раза в раз замечаю общие для многих ~~ошибки~~ зоны роста.

=======================================
Например, до сих пор встречается такой зверь:

```python
admins = []
for user in get_users():  
	if user.is_admin:      
		admins.append(user)
```

### 1. Использовать comprehension

Код выше можно написать короче, используя генератор списка.

```python
admins = [user for user in get_users() if user.is_admin]
```

Более того, такое решение будет работать немного быстрее, так как comprehension-ы - оптимизированы для создания объектов. Не забываем также, что подобным образом можно создавать словари и множества. 

Эта же конструкция признается сообществом как более _pythonic way_ для фильтрации

```python
# Вместо
active_users = list(filter(lambda user: user.is_active, get_users()))

# Лучше
active_users = [user for user in get_users() if user.is_active]
```

### 2. Вложенные циклы for в comprehension

Предположим, у нас есть задача сформировать список комбинаций элементов двух коллекций.

```python
numbers = ["1", "2", "3"]letters = ["A", "B", "C"]
# Хотим ['A1', 'A2', 'A3', 'B1', 'B2', 'B3', 'C1', 'C2', 'C3']
```

Даже знакомые с генераторами списков могу не знать о небольшом сахаре и написать вложенный цикл.

```python
spots = []for letter in letters:    
	for number in numbers:        
		spots.append(letter + number)

# Хотя и можно 
такspots = [letter + number for letter in letters for number in numbers]
```

### 3. Логические операторы вместо тернарного

Логические операторы часто можно использовать для задания значения переменной. Многие знакомы с тернарным оператором и избегают лишних if-else, однако и от первого можно уйти в некоторых случаях.

```python
# Есть переменная, которая может быть не заполнена
user: User | None

# Задаем админа тернарником
admin = user if user else get_user()

# Но можно и короче
admin = user or get_user()

# Цепочку можно продолжать. Вернется первое приводимое к True значение
admin = get_admin() or user or get_user()
```

### 4. Использование оптимизации проверки условий

Если python явно узнаёт значение условия, он не производит дальнейших проверок.

Пример. Мы хотим убедиться, что последний из аппрувнувших людей обладает админскими правами. Но список аппрувнувших может быть и пустым.

Чтобы не получить `"IndexError: list index out of range"`,  
часто вижу, что начинающие разработчики сначала проверяют список на пустоту

```python
approvers: list[User] = []if approvers:    
	if approvers[-1].is_admin:        
		return Truereturn False
```

Однако мы можем и совместить условия, упростив наш код:

```python
approvers = []
if approvers and approvers[-1].is_admin:    
	return Truereturn False
```

Здесь, если `approvers` пустой, то общее условие уже никак не может быть `True`. Поэтому дальнейшие проверки не производятся, и за границы списка мы не выходим.

Работает также и в обратную сторону для `or`

```python
approvers = []
# Если первое условие уже `True`, то часть с `or` не имеет значения
approved_by_admin = True
if approved_by_admin == True or approvers[-1].is_admin:    
	return Truereturn False
```

### 5. Использование магии bool

Как ни странно, в python `True` и `1` равны. Как и `False` c `0`

```python
>>> True == 1
>>> True
>>> False == 0
>>> True
```

Помимо багов, это может и здорово сыграть нам на руку. Снова пример.

Представим, что мы решили бросить программирование и ударились ростовщичество. Математическое прошлое подсказывает, что нужна какая-то система принятия решения о выдаче кредита, и мы выбираем самую передовую из известных нам.   
А именно: мы собираемся анализировать три показателя у заемщика.

- наличие дома
- наличие машины
- наличие работы

Если какие-либо два из них положительны, то клиент, скорее всего, кредит отдаст, поэтому мы дадим ему деньги. Приступаем!

```python
has_a_house: 
	boolhas_as_car: 
		boolhas_a_job: 
			boolif has_a_house and has_a_car:    
				return Trueif has_a_house and has_a_job:    
					return Trueif has_a_car and has_a_job:    
						return True
```

Вроде работает, но смущает нездоровое количество if-ов... А если факторов будет не три, а 100, и для положительного ответа нужно будет набрать минимум 60 очков? Переписываем.

```python
factors = [has_a_house, has_as_car, has_a_job]
total = 0
for factor in factors:    
	if factor:        
		total += 1return total >= 2
```

Уже лучше, но если мы воспользуемся тем, что булево `True` равняется единице, то получим изящный однострочник

```python
return sum(factors) >= 2
# здесь sum сложит все единицы. По сути, посчитает количество True.
```

### 6. Использование any и all

Если бы в предыдущем примере мы захотели проверить условия на
- хотя бы одно
- все

Мы бы просто могли воспользоваться конструкциями `any` и `all`соответственно

```python
any([True, False, False])
# Trueany([False, False, False])
# Falseall([True, True, True])
# Trueall([True, True, False])
# False
```

### 7. Сниппет для проверки пути в JSON-е

Когда мы работаем с JSON-ами внешних сервисов, элементы в них могут быть опциональны. Причем бывает так, что дерево пути может оборваться в любой момент. 

В данном примере хотим проверить наличие прав у пользователя на создание других пользователей. Для таких случаев сталкивался с таким вот кодом:

```python
user_dict = {    
	"user": {        
		"username": "PetyaNagibator228",        
		"permissions": {            
			"create_troubles": True,            
			"create_users": True        
			}    
		}
	}
if user := user_dict.get("user"):    
	if permissions := user.get("permissions"):        
		if permissions.get("create_users"):            
			return Truereturn False
```

Громоздко. Еще моржовые операторы эти. Было бы здорово обращаться к элементу прям по пути

`path = "user.permissions.create_users"`

Такое можно реализовать при помощи однострочника

```python
path = "user.permissions.create_users"
reduce(dict.get, path.split('.'), user_dict)
# True
```

Однако у него есть несколько недостатков. 

- reduce у новичков вызывает недопонимания
    
- решение не гибкое, если мы хотим иметь больше возможностей по обработке элементов
    

Возможно, более адаптируемым под Ваши нужды окажется прямолинейное решение:

```python
def find(path: str, dict_: dict):    
	keys = path.split(".")    
	data = dict_    
	for key in keys:        
		try:            
			data = data.get(key)        
		except AttributeError:            
			return None    
		return datafind("user.permissions.create_users", user_dict)
 
# Truefind("user.permissions.no_key.create_users", user_dict)# None
```

### 8. Генераторы для экономии памяти и кода

Уверен, что в любом уважающем себя курсе рассказывают про генераторы и про то, зачем они нужны. Однако это не мешает многим начинающим ребятам обходить пагинацию следующим образом:

```python
j = {    
	"pagination": {        
		"page": 0,  # текущая страница        
		"limit": 3  # кол-во элементов на странице    
	},    
	"user_ids": [1, 2, 3]
}
	
users = []
limit = 3
page = 0
while True:    
	rsp = client.get_all_users(page=page, limit=limit)    
		if not rsp.get("user_ids"):        
			break    
	users.extend(rsp["user_ids"])    
	page += 1return users
```

Здесь создается список пользователей и наполняется до тех пор, пока внешний сервис отдает данные. Как только пагинация будет исчерпана, возвращается созданный список. 

Используя подобную конструкцию, мы должны понимать, что весь итоговый список пользователей будет храниться в памяти. Что как минимум не очень эффективно. К тому же, существуют расходы на постоянное расширение данного списка на каждой итерации.

Здесь как нельзя лучше находят свое применение те самые генераторы.

```
limit = 3page = 0while True:    rsp = client.get_all_users(page=page, limit=limit)    if not rsp.get("user_ids"):        break    yield rsp["user_ids"]    page += 1
```

Так мы возвращаем пользователей пачками, сэкономив ресурсы машины.

### 9. Ограничение пагинации

Существует такая вещь, как ограничение глубины рекурсии. Она нужна для избегания бесконечной вложенности. К сожалению, работая с внешними API, я не один раз сталкивался с багами, приводящими к бесконечным обходам пагинации. В примере выше цикл так и будет крутиться, если по какой-то причине не выполнится условие `break`. Например, если API будет всегда возвращать данные, не зависимо от передаваемого `page`.

Для таких случаев не лишним было бы иметь некий предохранитель, которым часто пренебрегают.

```
max_pages = 5for i in range(max_pages + 1):      rsp = client.get_all_users(page=page, limit=limit)    if not rsp.get("user_ids"):        break    if i >= max_pages:        raise RuntimeError("Too many pagination elements")    yield rsp["user_ids"]
```

### 10. А так же...

Последним пунктом хотел бы привести похожую [статью](https://habr.com/ru/articles/466441/), обозревающую частые ошибочные решения разработчиков.

### Итог

Конечно же, данный список не является исчерпывающим. Он также ни в коем разе не является никаким «топом самых страшных вещей в мире программирования» и весьма субъективен. Даже наоборот, я старался собрать больше «мелочи», незаслуженно обделяемые вниманием начинающих разработчиков. 

Благодарю прочитавших статью и желаю всем никогда не останавливаться на пути развития!

==========================

# Глючный код на Python: 10 самых распространенных ошибок, которые допускают разработчики

6 сен 2019 в 15:49

### О Python

  
Python — это интерпретируемый, объектно-ориентированный язык программирования высокого уровня с динамической семантикой. Встроенные структуры данных высокого уровня в сочетании с динамической типизацией и динамическим связыванием делают его очень привлекательным для БРПС (быстрой разработки прикладных средств), а также для использования в качестве скриптового и связующего языка для подключения существующих компонентов или сервисов. Python поддерживает модули и пакеты, тем самым поощряя модульность программы и повторное использование кода.  
  

### О данной статье

  
Простота и легкость в освоении данного языка может ввести разработчиков в заблуждение (особенно тех, кто еще только начинает изучать Python), так что можно упустить из виду некоторые важные тонкости и недооценить силу разнообразия возможных решений с помощью Python.  
  
Имея это в виду, в этой статье представлен «топ-10» тонких, трудных для обнаружения ошибок, которые могут допустить даже продвинутые разработчики Python.  
  

### Ошибка № 1: неправильное использование выражений в качестве значений по умолчанию для аргументов функций

  
Python позволяет указывать, что у функции могут быть необязательные аргументы, путем задания для них значения по умолчанию. Это, конечно, очень удобная особенность языка, но может привести к неприятным последствиям, если тип такого значения будет изменяемым. Например, рассмотрим следующее определение функции:  
  

```
>>> def foo(bar=[]):        # bar - это необязательный аргумент                             # и по умолчанию равен пустому списку....    bar.append("baz")    # эта строка может стать проблемой......    return bar
```

  
Распространенная ошибка в данном случае — это думать, что значение необязательного аргумента будет устанавливаться в значение по умолчанию каждый раз, как функция будет вызываться без значения для этого аргумента. В приведенном выше коде, например, можно предположить, что повторно вызывая функцию foo() (то есть без указания значения для агрумента bar), она всегда будет возвращать «baz», поскольку предполагается, что каждый раз, когда вызывается foo () (без указания аргумента bar), bar устанавливается в [ ] (т. е. новый пустой список).  
  
Но давайте посмотрим что же будет происходить на самом деле:  
  

```
>>> foo()["baz"]>>> foo()["baz", "baz"]>>> foo()["baz", "baz", "baz"]
```

  
А? Почему функция продолжает добавлять значение по умолчанию «baz» к существующему списку каждый раз, когда вызывается foo(), вместо того, чтобы каждый раз создавать новый список?  
  
Ответом на данный вопрос будет более глубокое понимание того, что творится у Python «под капотом». А именно: значение по умолчанию для функции инициализируется только один раз, во время определения функции. Таким образом, аргумент bar инициализируется по умолчанию (т. е. пустым списком) только тогда, когда foo() определен впервые, но последующие вызовы foo() (т. е. без указания аргумента bar) продолжат использовать тот же список, который был создан для аргумента bar в момент первого определения функции.  
  
Для справки, распространенным «обходным путем» для этой ошибки является следующее определение:  
  

```
>>> def foo(bar=None):...    if bar is None:		# or if not bar:...        bar = []...    bar.append("baz")...    return bar...>>> foo()["baz"]>>> foo()["baz"]>>> foo()["baz"]
```

  

### Ошибка № 2: неправильное использование переменных класса

  
Рассмотрим следующий пример:  
  

```
>>> class A(object):...     x = 1...>>> class B(A):...     pass...>>> class C(A):...     pass...>>> print A.x, B.x, C.x1 1 1
```

  
Вроде все в порядке.  
  

```
>>> B.x = 2>>> print A.x, B.x, C.x1 2 1
```

  
Ага, все как и ожидалось.  
  

```
>>> A.x = 3>>> print A.x, B.x, C.x3 2 3
```

  
Что за черт?! Мы же только изменили A.x. Почему же C.x тоже изменилось?  
  
В Python переменные класса обрабатываются как словари и следуют тому, что часто называют Порядком разрешения методов (MRO). Таким образом, в приведенном выше коде, поскольку атрибут x не найден в классе C, он будет найден в его базовых классах (только A в приведенном выше примере, хотя Python поддерживает множественное наследование). Другими словами, C не имеет своего собственного свойства x, независимого от A. Таким образом, ссылки на C.x фактически являются ссылками на A.x. Это будет вызывать проблемы, если не обрабатывать такие случаи должным образом. Так что при изучении Python обратите особое внимание на аттрибуты класса и работу с ними.  
  

### Ошибка № 3: неправильное указание параметров для блока исключения

  
Предположим, что у вас есть следующий кусок кода:  
  

```
>>> try:...     l = ["a", "b"]...     int(l[2])... except ValueError, IndexError:  # To catch both exceptions, right?...     pass...Traceback (most recent call last):  File "<stdin>", line 3, in <module>IndexError: list index out of range
```

  
Проблема здесь заключается в том, что выражение except не принимает список исключений, указанных таким образом. Скорее, в Python 2.x выражение «except Exception, e» используется для привязки исключения к необязательному второму заданному второму параметру (в данном случае e), чтобы сделать его доступным для дальнейшей проверки. В результате в приведенном выше коде исключение IndexError не перехватывается выражением except; скорее, вместо этого исключение заканчивается привязкой к параметру с именем IndexError.  
  
Правильный способ перехвата нескольких исключений с помощью выражения except — указать первый параметр в виде кортежа, содержащего все исключения, которые нужно перехватить. Кроме того, для максимальной совместимости используйте ключевое слово as, так как этот синтаксис поддерживается как в Python 2, так и в Python 3:  
  

```
>>> try:...     l = ["a", "b"]...     int(l[2])... except (ValueError, IndexError) as e:  ...     pass...>>>
```

  

### Ошибка № 4: непонимание правил области видимости Python

  
Область видимости в Python основана на так называемом правиле LEGB, которое является аббревиатурой Local (имена, назначенные любым способом внутри функции (def или lambda), и не объявленные глобальными в этой функции), Enclosing (имя в локальной области действия любых статически включающих функций (def или lambda), от внутреннего к внешнему), Global (имена, назначенные на верхнем уровне файла модуля, или путем выполнения global инструкции в def внутри файла), Built-in (имена, предварительно назначенные в модуле встроенных имен: open, range, SyntaxError ,...). Кажется достаточно просто, верно? Ну, на самом деле, есть некоторые тонкости в том, как это работает в Python, что подводит нас к общей более сложной проблеме программирования на Python ниже. Рассмотрим следующей пример:   
  

```
>>> x = 10>>> def foo():...     x += 1...     print x...>>> foo()Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 2, in fooUnboundLocalError: local variable 'x' referenced before assignment
```

  
В чем проблема?  
  
Вышеуказанная ошибка возникает потому, что, когда вы присваиваете переменную в области видимости, Python автоматически считает ее локальной для этой области и скрывает любую переменную с аналогичным именем в любой вышестоящей области.  
  
Таким образом, многие удивляются, когда получают UnboundLocalError в ранее работающем коде, когда он модифицируется путем добавления оператора присваивания где-нибудь в теле функции.  
  
Эта особенность особенно сбивает разработчиков с толку при использовании списков. Рассмотрим следующий пример:  
  

```
>>> lst = [1, 2, 3]>>> def foo1():...     lst.append(5)   # Это работает нормально......>>> foo1()>>> lst[1, 2, 3, 5]>>> lst = [1, 2, 3]>>> def foo2():...     lst += [5]      # ... а вот это падает!...>>> foo2()Traceback (most recent call last):  File "<stdin>", line 1, in <module>  File "<stdin>", line 2, in fooUnboundLocalError: local variable 'lst' referenced before assignment
```

  
А? Почему foo2 падает, в то время как foo1 работает нормально?  
  
Ответ такой же, как в предыдущем примере, но, по распространенному мнению, здесь ситуация более тонкая. foo1 не применяет оператор присваивания к lst, тогда как foo2 — да. Помня, что lst + = [5] на самом деле является просто сокращением для lst = lst + [5], мы видим, что мы пытаемся присвоить значение lst (поэтому Python предполагает, что он находится в локальной области видимости). Однако значение, которое мы хотим присвоить lst, основано на самом lst (опять же, теперь предполагается, что он находится в локальной области видимости), который еще не был определен. И мы получаем ошибку.  
  

### Ошибка № 5: изменение списка во время итерации по нему

  
Проблема в следующем куске кода должна быть достаточно очевидной:  
  

```
>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> for i in range(len(numbers)):...     if odd(numbers[i]):...         del numbers[i]  # BAD: Deleting item from a list while iterating over it...Traceback (most recent call last):  	  File "<stdin>", line 2, in <module>IndexError: list index out of range
```

  
Удаление элемента из списка или массива во время итерации по нему — это проблема Python, которая хорошо известна любому опытному разработчику программного обеспечения. Но, хотя приведенный выше пример может быть достаточно очевидным, даже опытные разработчики могут встать на эти грабли в гораздо более сложном коде.  
  
К счастью, Python включает в себя ряд элегантных парадигм программирования, которые при правильном использовании могут привести к значительному упрощению и оптимизации кода. Дополнительным приятным следствием этого является то, что в более простом коде вероятность попасться на ошибку случайного удаления элемента списка во время итерации по нему значительно меньше. Одна из таких парадигм — генераторы списков. Кроме того, понимание работы генераторов списков особенно полезны для избежания этой конкретной проблемы, как показано в этой альтернативной реализацией приведенного выше кода, которая прекрасно работает:  
  

```
>>> odd = lambda x : bool(x % 2)>>> numbers = [n for n in range(10)]>>> numbers[:] = [n for n in numbers if not odd(n)]  # ahh, the beauty of it all>>> numbers[0, 2, 4, 6, 8]
```

  

### Ошибка № 6: непонимание того, как Python связывает переменные в замыканиях

  
Рассмотрим следующий пример:  
  

```
>>> def create_multipliers():...     return [lambda x : i * x for i in range(5)]>>> for multiplier in create_multipliers():...     print multiplier(2)...
```

  
Вы можете ожидать следующий вывод:  
  

```
02468
```

  
Но на самом деле вы получите вот что:  
  

```
88888
```

  
Сюрприз!  
  
Это происходит из-за поздней привязки в Python, которое заключается в том, что значения переменных, используемых в замыканиях, ищутся во время вызова внутренней функции. Таким образом, в приведенном выше коде всякий раз, когда вызывается какая-либо из возвращаемых функций, значение i ищется в окружающей области видимости во время ее вызова (а к тому времени цикл уже завершился, поэтому i уже был присвоен конечный результат — значение 4).  
  
Решение этой распространенной проблемы с Python будет таким:  
  

```
>>> def create_multipliers():...     return [lambda x, i=i : i * x for i in range(5)]...>>> for multiplier in create_multipliers():...     print multiplier(2)...02468
```

  
Вуаля! Мы используем здесь аргументы по умолчанию для генерации анонимных функций для достижения желаемого поведения. Некоторые назвали бы это решение элегантным. Некоторые —   
тонким. Некоторые ненавидят подобные штуки. Но если вы разработчик Python, в любом случае, это важно понимать.  
  

### Ошибка № 7: создание циклических зависимостей модуля

  
Допустим, у вас есть два файла, a.py и b.py, каждый из которых импортирует другой, следующим образом:  
  
В a.py:  
  

```
import bdef f():    return b.x	print f()
```

  
В b.py:  
  

```
import ax = 1def g():    print a.f()
```

  
Сначала попробуем импортировать a.py:  
  

```
>>> import a1
```

  
Сработало просто отлично. Возможно, это вас удивляет. В конце концов, модули циклически импортируют друг друга и это, вероятно, должено быть проблемой, не так ли?  
  
Ответ заключается в том, что простое наличие циклического импорта модулей само по себе не является проблемой в Python. Если модуль уже был импортирован, Python достаточно умен, чтобы не пытаться повторно импортировать его. Однако, в зависимости от точки, в которой каждый модуль пытается получить доступ к функциям или переменным, определенным в другом, вы действительно можете столкнуться с проблемами.  
  
Итак, возвращаясь к нашему примеру, когда мы импортировали a.py, у него не было проблем с импортом b.py, поскольку b.py не требует, чтобы что-либо из a.py было определено во время его импорта. Единственная ссылка в b.py на a — это вызов a.f(). Но этот вызов в g() и ничего в a.py или b.py не вызывает g(). Так что все работает прекрасно.  
  
Но что произойдет, если мы попытаемся импортировать b.py (без предварительного импорта a.py, то есть):  
  

```
>>> import bTraceback (most recent call last):  	  File "<stdin>", line 1, in <module>  	  File "b.py", line 1, in <module>    import a  	  File "a.py", line 6, in <module>	print f()  	  File "a.py", line 4, in f	return b.xAttributeError: 'module' object has no attribute 'x'
```

  
Ой-ой. Это не хорошо! Проблема здесь в том, что в процессе импорта b.py он пытается импортировать a.py, который, в свою очередь, вызывает f(), который пытается получить доступ к b.x. Но b.x еще не было определено. Отсюда исключение AttributeError.  
  
По крайней мере, одно из решений этой проблемы довольно тривиально. Просто измените b.py, чтобы импортировать a.py в g():  
  

```
x = 1def g():    import a	# This will be evaluated only when g() is called    print a.f()
```

  
Теперь, когда мы его импортируем, все нормально:  
  

```
>>> import b>>> b.g()1	# Printed a first time since module 'a' calls 'print f()' at the end1	# Printed a second time, this one is our call to 'g'
```

  

### Ошибка № 8: пересечение имен с именами модулями стандартной библиотеки Python

  
Одна из прелестей Python — это множество модулей, которые поставляются «из коробки». Но в результате, если вы сознательно не будете за этим следить, можно столкнуться с тем, что имя вашего модуля может быть с тем же именем, что и модуль в стандартной библиотеке, поставляемой с Python (например, в вашем коде может быть модуль с именем email.py, который будет конфликтовать со модулем стандартной библиотеки с таким же именем).  
  
Это может привести к серьезным проблемам. Например, если какой-нибудь из модулей будет пытаться импортировать версию модуля из стандартной библиотеки Python, а у вас в проекте будет модуль с таким же именем, который и будет по ошибке импортирован вместо модуля из стандартной библиотеки.   
  
Поэтому следует проявлять осторожность, чтобы не использовать те же имена, что и в модулях стандартной библиотеки Python. Гораздо проще изменить название модуля в своем проекте, нежели подать запрос на изменение имени модуля в стандартной библиотеке и получить на него одобрение.  
  

### Ошибка № 9: неспособность учесть различия Python 2 и Python 3

  
Рассмотрим следующий файл foo.py:  
  

```
import sysdef bar(i):    if i == 1:        raise KeyError(1)    if i == 2:        raise ValueError(2)def bad():    e = None    try:        bar(int(sys.argv[1]))    except KeyError as e:        print('key error')    except ValueError as e:        print('value error')    print(e)bad()
```

  
На Python 2 он отработает нормально:  
  

```
$ python foo.py 1key error1$ python foo.py 2value error2
```

  
Но теперь давайте посмотрим как он будет работать в Python 3:  
  

```
$ python3 foo.py 1key errorTraceback (most recent call last):  File "foo.py", line 19, in <module>    bad()  File "foo.py", line 17, in bad    print(e)UnboundLocalError: local variable 'e' referenced before assignment
```

  
Что здесь только что произошло? «Проблема» в том, что в Python 3 объект в блоке исключения недоступен за его пределами. (Причина этого заключается в том, что в противном случае объекты в этом блоке будут сохраняться в памяти до тех пор, пока сборщик мусора не запустится и не удалит ссылки на них оттуда).  
  
Один из способов избежать этой проблемы — сохранить ссылку на объект блока исключения за пределами этого блока, чтобы он оставался доступным. Вот версия предыдущего примера, которая использует эту технику, тем самым получая код, который подходит как для Python 2, так и для Python 3:  
  

```
import sysdef bar(i):    if i == 1:        raise KeyError(1)    if i == 2:        raise ValueError(2)def good():    exception = None    try:        bar(int(sys.argv[1]))    except KeyError as e:        exception = e        print('key error')    except ValueError as e:        exception = e        print('value error')    print(exception)good()
```

  
Запустим его в Python 3:  
  

```
$ python3 foo.py 1key error1$ python3 foo.py 2value error2
```

  
Ураааа!  
  

### Ошибка № 10: неправильное использование метода __del__

  
Допустим, у вас есть вот такой файл mod.py:  
  

```
import fooclass Bar(object):   	    ...    def __del__(self):        foo.cleanup(self.myhandle)
```

  
И вы пытаетесь сделать вот такое из другого another_mod.py:  
  

```
import modmybar = mod.Bar()
```

  
И получите ужасный AttributeError.  
  
Почему? Потому что, как сообщается [здесь](https://mail.python.org/pipermail/python-bugs-list/2009-January/069209.html), когда интерпретатор отключается, глобальные переменные модуля все имеют значение None. В результате в приведенном выше примере, в момент вызова __del__, имя foo уже было установлено в None.  
  
Решением этой «задачи со звездочкой» будет использование atexit.register(). Таким образом, когда ваша программа завершает выполнение (то есть при нормальном выходе из нее), ваши handle'ы удаляются до того, как интерпретатор звершает работу.  
  
С учетом этого, исправление для приведенного выше кода mod.py может выглядеть примерно так:  
  

```
import fooimport atexitdef cleanup(handle):    foo.cleanup(handle)class Bar(object):    def __init__(self):        ...        atexit.register(cleanup, self.myhandle)
```

  
Подобная реализация обеспечивает простой и надежный способ вызова любой необходимой очистки после обычного завершения программы. Очевидно, что решение о том, как поступить с объектом, который связан с имненем self.myhandle, остается за foo.cleanup, но, думаю, идею вы поняли.  


![[telegram-cloud-photo-size-2-5386535536837126461-y.jpg]]