Python FTP hangs in callback - python

I'm using ftplib to create a simple script to push out a file to multiple IP addresses, all set up as FTP servers. I wanted to display progress in the file upload process, but I'm having an issue. I use the callback argument of FTP.storbinary() and it works with something like this:
count = 0
def update(block):
count2 = str(count + 1)
print count2
However, if I try to do any arithmetic outside of a str() call, the program hangs. So the following doesn't work:
count = 0
def update(block):
count += 1
print count
Even wrapping count in a str() call doesn't work. It just hangs on the first call.

If you just try calling update yourself, instead of passing it to FTP.storbinary, you'll see the problem immediately:
>>> update('')
UnboundLocalError: local variable 'count' referenced before assignment
If you want to update a global variable, you have to mark it global explicitly:
def update(block):
global count
count += 1
print count
See the FAQ entry Why am I getting an UnboundLocalError when the variable has a value? and the following question What are the rules for local and global variables in Python?, and the docs on global, for more details.
A better way to solve this would be to write a class:
class FtpHandler(object):
def __init__(self):
self.count = 0
def update(self, block):
self.count += 1
print self.count
Then, to use it, you construct an instance of the class, and pass a bound method instead of a plain function to the FTP code. For example, instead of this:
ftp = ftplib.FTP(...)
# ...
ftp.storbinary(spam, eggs, callback=update)
… do this:
myhandler = FtpHandler()
ftp = ftplib.FTP(...)
# ...
ftp.storbinary(spam, eggs, callback=myhandler.update)

It doesn't just hang, it produces an Exception (specifically an UnboundLocalError). You're trying to modify a global variable inside of a function; to do this the variable must be declared global:
count = 0
def update(block):
global count
count += 1
print count
This is almost always a sign of bad design, in your case it would probably be better to use a class with an attribute:
class MyCallbackHandler(object):
def __init__(self):
self.count = 0
def update(self, block):
self.count += 1
#... etc.

Related

NameError in functions using global variable

I try to stop a .after function i tkinter in python 3.6, but have probems with namspace of variables. I get NameError: "name 'counter' is not defined" when I use this code:
I read this excellent post, from where I got the idea of using two buttons instead of one.
Use start and stop function with same button in Tkinter
def count(ac,rs):
if ac:
global counter
counter += 1
label.config(text=counter)
label.after(1000, count)
if rs:
counter = 0
counter += 1
count()
def start():
ac = True
rs=False
count(ac,rs)
def stop():
ac = False
label.configure(text='0')
rs = True
count(ac,rs)
Since I declare the variable counter in the count() function, I don't understand the NameError.

Is there a way to fix Name Error due to scope?

I have a function that creates a player object but when referencing the object, I get a NameError. I think it is happening due to local scope but global should fix it...
I just started out OOP and this code is working in the python shell but it is not working in script mode.
endl = lambda a: print("\n"*a)
class Score:
_tie = 0
def __init__(self):
self._name = ""
self._wins = 0
self._loses = 0
def get_name(self):
print
self._name = input().upper()
def inc_score(self, wlt):
if wlt=="w": self._wins += 1
elif wlt=="l": self._loses += 1
elif wlt=="t": _tie += 1
else: raise ValueError("Bad Input")
def player_num(): #Gets number of players
while True:
clear()
endl(10)
print("1 player or 2 players?")
endl(5)
pnum = input('Enter 1 or 2: '.rjust(55))
try:
assert int(pnum) == 1 or int(pnum) == 2
clear()
return int(pnum)
except:
print("\n\nPlease enter 1 or 2.")
def create_player(): #Creates players
global p1
p1 = Score()
yield 0 #stops here if there is only 1 player
global p2
p2 = Score()
def pr_(): #testing object
input(p1._wins)
input(p2._wins)
for i in range(player_num()):
create_player()
input(p1)
input(p1._wins())
pr_()
wherever I reference p1 I should get the required object attributes but I'm getting this error
Traceback (most recent call last):
File "G:/Python/TicTacTwo.py", line 83, in <module>
input(p1)
NameError: name 'p1' is not defined
Your issue is not with global but with the yield in create_player(), which turns the function into a generator.
What you could do:
Actually run through the generator, by executing list(create_player()) (not nice, but works).
But I suggest you re-design your code instead, e.g. by calling the method with the number of players:
def create_player(num): #Creates players
if num >= 1:
global p1
p1 = Score()
if num >= 2:
global p2
p2 = Score()
If you fix this issue, the next issues will be
1) input(p1) will print the string representation of p1 and the input will be lost, you probably want p1.get_name() instead.
2) input(p1._wins()) will raise TypeError: 'int' object is not callable
I will redesign the app to introduce really powerful python constructs that may help you when getting into OOP.
Your objects are going to represent players, then don't call them Score, call them Player.
Using _tie like that makes it a class variable, so the value is shared for all the players. With only two participants this may be true but this will come to hurt you when you try to extend to more players. Keep it as a instance variable.
I am a fan of __slots__. It is a class special variable that tells the instance variables what attributes they can have. This will prevent to insert new attributes by mistake and also improve the memory needed for each instance, you can remove this line and it will work but I suggest you leave it. __slots__ is any kind of iterable. Using tuples as they are inmutable is my recomendation.
Properties are also a really nice feature. They will act as instance attribute but allow you to specify how they behave when you get the value (a = instance.property), assign them a value (instance.property = value), or delete the value (del instance.property). Name seems to be a really nice fit for a property. The getter will just return the value stored in _name, the setter will remove the leading and trailing spaces and will capitalize the first letter of each word, and the deletter will set the default name again.
Using a single function to compute a result is not very descriptive. Let's do it with 3 functions.
The code could look like this:
# DEFAULT_NAME is a contant so that we only have to modify it here if we want another
# default name instead of having to change it in several places
DEFAULT_NAME = "Unknown"
class Player:
# ( and ) are not needed but I'll keep them for clarity
__slots__ = ("_name", "_wins", "_loses", "_ties")
# We give a default name in case none is provided when the instance is built
def __init__(self, name=DEFAULT_NAME):
self._name = name
self._wins = 0
self._loses = 0
self._ties = 0
# This is part of the name property, more specifically the getter and the documentation
#property
def name(self):
""" The name of the player """
return self._name
# This is the setter of the name property, it removes spaces with .strip() and
# capitalizes first letters of each word with .title()
#name.setter
def name(self, name):
self._name = name.strip().title()
# This is the last part, the deleter, that assigns the default name again
#name.deleter
def name(self):
self._name = DEFAULT_NAME
def won(self):
self._wins += 1
def lost(self):
self._loses += 1
def tied(self):
self._ties += 1
Now that's all we need for the player itself. The game should have a different class where the players are created.
class Game:
_min_players = 1
_max_players = 2
def __init__(self, players):
# Check that the number of players is correct
if not(self._min_players <= players <= self._max_players):
raise ValueError("Number of players is invalid")
self._players = []
for i in range(1, players+1):
self._players.append(Player(input("Insert player {}'s name: ".format(i))))
#property
def players(self):
# We return a copy of the list to avoid mutating the inner list
return self._players.copy()
Now the game would be created as follows:
def new_game():
return Game(int(input("How many players? ")))
After that you would create new methods for the game like playing matches that will call the players won, lost or tied method, etc.
I hope that some of the concepts introduced here are useful for you, like properties, slots, delegating object creation to the owner object, etc.

python function string parameter convert to variable name

I'm rather new to Python and programming in general, so I apologise in advance if my terminology is incorrect.
hue_alert_delay = 0
def delays(name, delay):
global hue_alert_delay
if name == 'hue_alert_delay':
for i in range(0, delay):
hue_alert_delay += 1
time.sleep(1)
hue_alert_delay = 0
delays('hue_alert_delay', 60)
What I'm trying to achieve:
I would like the function to convert the 'name' parameter, which is a string input, into a pre-exiting variable, which will negate the need for multiple IF statements.
The above example includes only one IF statement, but for my project there will be a lot more and I would rather keep the function clean and simple.
This won't work, but it's what I'm trying to aim for:
hue_alert_delay = 0
def delays(name, delay):
global name
for i in range(0, delay):
name += 1
time.sleep(1)
hue_alert_delay = 0
delays('hue_alert_delay', 60)
Any assistance would be appreciated.
Use a dict:
values = {
'hue_alert_delay': 0
}
def delays(name, delay):
values[name] += 1
Whenever you feel like using "variable variables", what you most likely really want is a dict storing key-value associations. Yes, there are other ways to do literally what you want, but that soon leads to insane code.
Use a dictionary like so.
vars = {'hue_alert_delay':0}
def delays(name, delay):
for i in range(0, delay):
vars[name] += 1
time.sleep(1)
vars[name] = 0
You can also use globals()[name] but I won't recommend it.
Use a dictionary:
vars = {'hue_alert_delay':0}
def delays(name, delay):
for i in range(delay):
vars[name] += 1
time.sleep(1)
vars[name] = 0
delays('hue_alert_delay', 60)

Python OOP, using loops to number objects as they are created

I'm stumped on a python problem. I'm writing a program that receives a command from Scratch (MIT) and then should create a new object, in this case named PiLight. The object only need to be created when the command is received so it doesn't have to loop, just be able to executed repeatedly and have the number increment each time it is executed.A list will not work for me due to the requirements of the program and talking between Scratch. I'm trying to figure out a way for the constructor, once initialized, to print out a statement something like
class Newpilight:
def __init__(self):
print "Pilight" + pilnumber + " created"
pilnumber should be 1 for 1st object, 2 for 2nd, etc
From there I need the creation of the object to change the number in the name of the object as well
PiLight(PiLnumber) = Newpilight()
I tried messing around with for loops but just ended up making more of a mess
Use number generator as class variable
from itertools import count
class NewPilight(object):
nums = count()
def __init__(self):
self.num = self.nums.next()
print "Pilight {self.num} created".format(self=self)
Then using in code:
>>> pl1 = NewPilight()
Pilight 0 created
>>> pl2 = NewPilight()
Pilight 1 created
>>> pl3 = NewPilight()
Pilight 2 created
>>> pl3.num
2
The trick is to have the nums (what is actually a generator of numbers, not list of numbers) as class property and not property of class instance. This way it is globally shared by all class instances.
class NewPilight:
def __init__(self, number):
self.number = number
print "Pilight" + number + " created"
for x in range(5):
NewPilight(x)
if you need to keep objects:
all_pilights = []
for x in range(5):
all_pilights.append( NewPilight(x) )
and now you have access to objects as
print all_pilights[0].number
print all_pilights[1].number
print all_pilights[2].number
class NewPiLight(object):
global_pilnumber = 0 # Since this is on the class definition, it is static
def __init__(self):
print "Pilight %s created" % NewPiLight.global_pilnumber
self.pilnumber = NewPiLight.global_pilnumber # Set the variable for this instance
NewPiLight.global_pilnumber += 1 # This increments the static variable

Increment variable in callback during upload

I have the following python script for an upload that needs to show percent done. I am having trouble incrementing the variable that tracks the amount of data transferred.
I get an
UnboundLocalError: local variable 'intProgress' referenced before assignment
error. Yet if I try to print this variable it prints fine so it seems that it is referenced.
import os, sys, ftplib
pathname = 'C:/Paradigm1/1.PNG'
intFileSize = os.path.getsize(pathname)
intPercentDone = 0
intProgress = 0
def callback(p):
intProgress = intProgress + 1024
##sys.stdout.write(str(intProgress))
sys.stdout.write("-")
session = ftplib.FTP('Server','UserName','Password')
f = open(pathname,'rb')# file to send
session.storbinary('STOR /Ftp Accounts/PublicDownloads/test.png', f, 1024, callback)
f.close()
If you want the callback() function to change the global variable intProgress, you have to declare it as global in the function...
def callback(p):
global intProgress
intProgress = intProgress + 1024
##sys.stdout.write(str(intProgress))
sys.stdout.write("-")
...otherwise it'll assume intProgress is a local variable, and get confused by the fact that you're trying to reference it when setting it.
intProgress = inside a function forces Python to treat it as a local variable overshadowing the variable from the outer scope.
To avoid mutable globals, you could create a closure:
import os
import sys
def make_callback(filesize):
total = [0] # use list to emulate nonlocal keyword
width = len(str(filesize))
def report_progress(block):
total[0] += len(block)
sys.stderr.write("\r{:{}d} / {}".format(total[0], width, filesize))
return report_progress
def main():
# ...
ftp.storbinary(..., make_callback(os.path.getsize(filename)))
main()

Categories